littlejsengine 1.9.4 → 1.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LittleJS.zip +0 -0
- package/dist/littlejs.d.ts +16 -18
- package/dist/littlejs.esm.js +131 -56
- package/dist/littlejs.esm.min.js +1 -1
- package/dist/littlejs.js +131 -56
- package/dist/littlejs.min.js +1 -1
- package/dist/littlejs.release.js +130 -55
- package/examples/platformer/gameEffects.js +2 -3
- package/package.json +1 -1
- package/reference.md +11 -11
- package/src/engine.js +66 -33
- package/src/engineBuild.js +4 -4
- package/src/engineDebug.js +1 -1
- package/src/engineObject.js +9 -11
- package/src/engineTileLayer.js +6 -3
- package/src/engineUtilities.js +49 -8
package/LittleJS.zip
ADDED
|
Binary file
|
package/dist/littlejs.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare module "
|
|
1
|
+
declare module "littlejsengine" {
|
|
2
2
|
/**
|
|
3
3
|
* LittleJS - The Tiny Fast JavaScript Game Engine
|
|
4
4
|
* MIT License - Copyright 2021 Frank Force
|
|
@@ -79,7 +79,7 @@ declare module "littlejs.esm" {
|
|
|
79
79
|
* @memberof Engine */
|
|
80
80
|
export function engineObjectsDestroy(): void;
|
|
81
81
|
/** Triggers a callback for each object within a given area
|
|
82
|
-
* @param {Vector2} [pos] - Center of test area
|
|
82
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
83
83
|
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
84
84
|
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
85
85
|
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
@@ -619,13 +619,13 @@ declare module "littlejs.esm" {
|
|
|
619
619
|
* @memberof Utilities */
|
|
620
620
|
export function nearestPowerOfTwo(value: number): number;
|
|
621
621
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
622
|
-
* @param {Vector2}
|
|
623
|
-
* @param {Vector2} sizeA
|
|
624
|
-
* @param {Vector2}
|
|
625
|
-
* @param {Vector2} [sizeB=(0,0)]
|
|
626
|
-
* @return {Boolean}
|
|
622
|
+
* @param {Vector2} posA - Center of box A
|
|
623
|
+
* @param {Vector2} sizeA - Size of box A
|
|
624
|
+
* @param {Vector2} posB - Center of box B
|
|
625
|
+
* @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
|
|
626
|
+
* @return {Boolean} - True if overlapping
|
|
627
627
|
* @memberof Utilities */
|
|
628
|
-
export function isOverlapping(
|
|
628
|
+
export function isOverlapping(posA: Vector2, sizeA: Vector2, posB: Vector2, sizeB?: Vector2): boolean;
|
|
629
629
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
630
630
|
* @param {Number} [frequency] - Frequency of the wave in Hz
|
|
631
631
|
* @param {Number} [amplitude] - Amplitude (max height) of the wave
|
|
@@ -1637,6 +1637,8 @@ declare module "littlejs.esm" {
|
|
|
1637
1637
|
collideSolidObjects: boolean;
|
|
1638
1638
|
/** @property {Boolean} - Object collides with and blocks other objects */
|
|
1639
1639
|
isSolid: boolean;
|
|
1640
|
+
/** @property {Boolean} - Object collides with raycasts */
|
|
1641
|
+
collideRaycast: boolean;
|
|
1640
1642
|
/** Update the object transform and physics, called automatically by engine once each frame */
|
|
1641
1643
|
update(): void;
|
|
1642
1644
|
groundObject: any;
|
|
@@ -1650,11 +1652,6 @@ declare module "littlejs.esm" {
|
|
|
1650
1652
|
* @param {Vector2} pos - tile where the collision occured
|
|
1651
1653
|
* @return {Boolean} - true if the collision should be resolved */
|
|
1652
1654
|
collideWithTile(tileData: number, pos: Vector2): boolean;
|
|
1653
|
-
/** Called to check if a tile raycast hit
|
|
1654
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1655
|
-
* @param {Vector2} pos - tile where the raycast is
|
|
1656
|
-
* @return {Boolean} - true if the raycast should hit */
|
|
1657
|
-
collideWithTileRaycast(tileData: number, pos: Vector2): boolean;
|
|
1658
1655
|
/** Called to check if a object collision should be resolved
|
|
1659
1656
|
* @param {EngineObject} object - the object to test against
|
|
1660
1657
|
* @return {Boolean} - true if the collision should be resolved
|
|
@@ -1681,10 +1678,11 @@ declare module "littlejs.esm" {
|
|
|
1681
1678
|
* @param {EngineObject} child */
|
|
1682
1679
|
removeChild(child: EngineObject): void;
|
|
1683
1680
|
/** Set how this object collides
|
|
1684
|
-
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
|
|
1685
|
-
* @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
|
|
1686
|
-
* @param {Boolean} [collideTiles] - Does it collide with the tile collision
|
|
1687
|
-
|
|
1681
|
+
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
1682
|
+
* @param {Boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
1683
|
+
* @param {Boolean} [collideTiles] - Does it collide with the tile collision?
|
|
1684
|
+
* @param {Boolean} [collideRaycast] - Does it collide with raycasts? */
|
|
1685
|
+
setCollision(collideSolidObjects?: boolean, isSolid?: boolean, collideTiles?: boolean, collideRaycast?: boolean): void;
|
|
1688
1686
|
/** Returns string containg info about this object for debugging
|
|
1689
1687
|
* @return {String} */
|
|
1690
1688
|
toString(): string;
|
|
@@ -1729,7 +1727,7 @@ declare module "littlejs.esm" {
|
|
|
1729
1727
|
* @return {Boolean}
|
|
1730
1728
|
* @memberof TileCollision */
|
|
1731
1729
|
export function tileCollisionTest(pos: Vector2, size?: Vector2, object?: EngineObject): boolean;
|
|
1732
|
-
/** Return the center of tile
|
|
1730
|
+
/** Return the center of first tile hit (does not return the exact intersection)
|
|
1733
1731
|
* @param {Vector2} posStart
|
|
1734
1732
|
* @param {Vector2} posEnd
|
|
1735
1733
|
* @param {EngineObject} [object]
|
package/dist/littlejs.esm.js
CHANGED
|
@@ -357,7 +357,7 @@ function debugRender()
|
|
|
357
357
|
overlayContext.restore();
|
|
358
358
|
});
|
|
359
359
|
|
|
360
|
-
// remove expired
|
|
360
|
+
// remove expired primitives
|
|
361
361
|
debugPrimitives = debugPrimitives.filter(r=>r.time<0);
|
|
362
362
|
}
|
|
363
363
|
|
|
@@ -542,16 +542,57 @@ function smoothStep(percent) { return percent * percent * (3 - 2 * percent); }
|
|
|
542
542
|
function nearestPowerOfTwo(value) { return 2**Math.ceil(Math.log2(value)); }
|
|
543
543
|
|
|
544
544
|
/** Returns true if two axis aligned bounding boxes are overlapping
|
|
545
|
-
* @param {Vector2}
|
|
546
|
-
* @param {Vector2} sizeA
|
|
547
|
-
* @param {Vector2}
|
|
548
|
-
* @param {Vector2} [sizeB=(0,0)]
|
|
549
|
-
* @return {Boolean}
|
|
545
|
+
* @param {Vector2} posA - Center of box A
|
|
546
|
+
* @param {Vector2} sizeA - Size of box A
|
|
547
|
+
* @param {Vector2} posB - Center of box B
|
|
548
|
+
* @param {Vector2} [sizeB=(0,0)] - Size of box B, a point if undefined
|
|
549
|
+
* @return {Boolean} - True if overlapping
|
|
550
550
|
* @memberof Utilities */
|
|
551
|
-
function isOverlapping(
|
|
551
|
+
function isOverlapping(posA, sizeA, posB, sizeB=vec2())
|
|
552
552
|
{
|
|
553
|
-
return abs(
|
|
554
|
-
&& abs(
|
|
553
|
+
return abs(posA.x - posB.x)*2 < sizeA.x + sizeB.x
|
|
554
|
+
&& abs(posA.y - posB.y)*2 < sizeA.y + sizeB.y;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/** Returns true if a line segment is intersecting an axis aligned box
|
|
558
|
+
* @param {Vector2} start - Start of raycast
|
|
559
|
+
* @param {Vector2} end - End of raycast
|
|
560
|
+
* @param {Vector2} pos - Center of box
|
|
561
|
+
* @param {Vector2} size - Size of box
|
|
562
|
+
* @return {Boolean} - True if intersecting
|
|
563
|
+
* @memberof Utilities */
|
|
564
|
+
function isIntersecting(start, end, pos, size)
|
|
565
|
+
{
|
|
566
|
+
// Liang-Barsky algorithm
|
|
567
|
+
const boxMin = pos.subtract(size.scale(.5));
|
|
568
|
+
const boxMax = boxMin.add(size);
|
|
569
|
+
const delta = end.subtract(start);
|
|
570
|
+
const a = start.subtract(boxMin);
|
|
571
|
+
const b = start.subtract(boxMax);
|
|
572
|
+
const p = [-delta.x, delta.x, -delta.y, delta.y];
|
|
573
|
+
const q = [a.x, -b.x, a.y, -b.y];
|
|
574
|
+
let tMin = 0, tMax = 1;
|
|
575
|
+
for (let i = 4; i--;)
|
|
576
|
+
{
|
|
577
|
+
if (p[i])
|
|
578
|
+
{
|
|
579
|
+
const t = q[i] / p[i];
|
|
580
|
+
if (p[i] < 0)
|
|
581
|
+
{
|
|
582
|
+
if (t > tMax) return false;
|
|
583
|
+
tMin = max(t, tMin);
|
|
584
|
+
}
|
|
585
|
+
else
|
|
586
|
+
{
|
|
587
|
+
if (t < tMin) return false;
|
|
588
|
+
tMax = min(t, tMax);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
else if (q[i] < 0)
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return true;
|
|
555
596
|
}
|
|
556
597
|
|
|
557
598
|
/** Returns an oscillating wave between 0 and amplitude with frequency of 1 Hz by default
|
|
@@ -1735,6 +1776,8 @@ class EngineObject
|
|
|
1735
1776
|
this.collideSolidObjects = false;
|
|
1736
1777
|
/** @property {Boolean} - Object collides with and blocks other objects */
|
|
1737
1778
|
this.isSolid = false;
|
|
1779
|
+
/** @property {Boolean} - Object collides with raycasts */
|
|
1780
|
+
this.collideRaycast = false;
|
|
1738
1781
|
|
|
1739
1782
|
// add to list of objects
|
|
1740
1783
|
engineObjects.push(this);
|
|
@@ -1939,13 +1982,7 @@ class EngineObject
|
|
|
1939
1982
|
* @param {Number} tileData - the value of the tile at the position
|
|
1940
1983
|
* @param {Vector2} pos - tile where the collision occured
|
|
1941
1984
|
* @return {Boolean} - true if the collision should be resolved */
|
|
1942
|
-
collideWithTile(tileData, pos)
|
|
1943
|
-
|
|
1944
|
-
/** Called to check if a tile raycast hit
|
|
1945
|
-
* @param {Number} tileData - the value of the tile at the position
|
|
1946
|
-
* @param {Vector2} pos - tile where the raycast is
|
|
1947
|
-
* @return {Boolean} - true if the raycast should hit */
|
|
1948
|
-
collideWithTileRaycast(tileData, pos) { return tileData > 0; }
|
|
1985
|
+
collideWithTile(tileData, pos) { return tileData > 0; }
|
|
1949
1986
|
|
|
1950
1987
|
/** Called to check if a object collision should be resolved
|
|
1951
1988
|
* @param {EngineObject} object - the object to test against
|
|
@@ -1992,16 +2029,18 @@ class EngineObject
|
|
|
1992
2029
|
}
|
|
1993
2030
|
|
|
1994
2031
|
/** Set how this object collides
|
|
1995
|
-
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects
|
|
1996
|
-
* @param {Boolean} [isSolid] - Does it collide with and block other objects (expensive in large numbers)
|
|
1997
|
-
* @param {Boolean} [collideTiles] - Does it collide with the tile collision
|
|
1998
|
-
|
|
2032
|
+
* @param {Boolean} [collideSolidObjects] - Does it collide with solid objects?
|
|
2033
|
+
* @param {Boolean} [isSolid] - Does it collide with and block other objects? (expensive in large numbers)
|
|
2034
|
+
* @param {Boolean} [collideTiles] - Does it collide with the tile collision?
|
|
2035
|
+
* @param {Boolean} [collideRaycast] - Does it collide with raycasts? */
|
|
2036
|
+
setCollision(collideSolidObjects=true, isSolid=true, collideTiles=true, collideRaycast=true)
|
|
1999
2037
|
{
|
|
2000
2038
|
ASSERT(collideSolidObjects || !isSolid, 'solid objects must be set to collide');
|
|
2001
2039
|
|
|
2002
2040
|
this.collideSolidObjects = collideSolidObjects;
|
|
2003
2041
|
this.isSolid = isSolid;
|
|
2004
2042
|
this.collideTiles = collideTiles;
|
|
2043
|
+
this.collideRaycast = collideRaycast;
|
|
2005
2044
|
}
|
|
2006
2045
|
|
|
2007
2046
|
/** Returns string containg info about this object for debugging
|
|
@@ -3654,7 +3693,7 @@ function tileCollisionTest(pos, size=vec2(), object)
|
|
|
3654
3693
|
}
|
|
3655
3694
|
}
|
|
3656
3695
|
|
|
3657
|
-
/** Return the center of tile
|
|
3696
|
+
/** Return the center of first tile hit (does not return the exact intersection)
|
|
3658
3697
|
* @param {Vector2} posStart
|
|
3659
3698
|
* @param {Vector2} posEnd
|
|
3660
3699
|
* @param {EngineObject} [object]
|
|
@@ -3850,8 +3889,11 @@ class TileLayer extends EngineObject
|
|
|
3850
3889
|
mainCanvas.height = mainCanvasSize.y;
|
|
3851
3890
|
}
|
|
3852
3891
|
|
|
3853
|
-
//
|
|
3854
|
-
|
|
3892
|
+
// disable smoothing for pixel art
|
|
3893
|
+
this.context.imageSmoothingEnabled = !canvasPixelated;
|
|
3894
|
+
|
|
3895
|
+
// setup gl rendering if enabled
|
|
3896
|
+
glEnable && glPreRender();
|
|
3855
3897
|
}
|
|
3856
3898
|
|
|
3857
3899
|
/** Call to end the redraw process */
|
|
@@ -5000,7 +5042,7 @@ const engineName = 'LittleJS';
|
|
|
5000
5042
|
* @type {String}
|
|
5001
5043
|
* @default
|
|
5002
5044
|
* @memberof Engine */
|
|
5003
|
-
const engineVersion = '1.9.
|
|
5045
|
+
const engineVersion = '1.9.5';
|
|
5004
5046
|
|
|
5005
5047
|
/** Frames per second to update
|
|
5006
5048
|
* @type {Number}
|
|
@@ -5067,6 +5109,19 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5067
5109
|
{
|
|
5068
5110
|
ASSERT(Array.isArray(imageSources), 'pass in images as array');
|
|
5069
5111
|
|
|
5112
|
+
// Called automatically by engine to setup render system
|
|
5113
|
+
function enginePreRender()
|
|
5114
|
+
{
|
|
5115
|
+
// save canvas size
|
|
5116
|
+
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5117
|
+
|
|
5118
|
+
// disable smoothing for pixel art
|
|
5119
|
+
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
5120
|
+
|
|
5121
|
+
// setup gl rendering if enabled
|
|
5122
|
+
glEnable && glPreRender();
|
|
5123
|
+
}
|
|
5124
|
+
|
|
5070
5125
|
// internal update loop for engine
|
|
5071
5126
|
function engineUpdate(frameTimeMS=0)
|
|
5072
5127
|
{
|
|
@@ -5224,7 +5279,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5224
5279
|
}
|
|
5225
5280
|
image.src = src;
|
|
5226
5281
|
})
|
|
5227
|
-
)
|
|
5282
|
+
);
|
|
5228
5283
|
|
|
5229
5284
|
// draw splash screen
|
|
5230
5285
|
showSplashScreen && promises.push(new Promise(resolve =>
|
|
@@ -5249,19 +5304,6 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5249
5304
|
});
|
|
5250
5305
|
}
|
|
5251
5306
|
|
|
5252
|
-
// Called automatically by engine to setup render system
|
|
5253
|
-
function enginePreRender()
|
|
5254
|
-
{
|
|
5255
|
-
// save canvas size
|
|
5256
|
-
mainCanvasSize = vec2(mainCanvas.width, mainCanvas.height);
|
|
5257
|
-
|
|
5258
|
-
// disable smoothing for pixel art
|
|
5259
|
-
mainContext.imageSmoothingEnabled = !canvasPixelated;
|
|
5260
|
-
|
|
5261
|
-
// setup gl rendering if enabled
|
|
5262
|
-
glEnable && glPreRender();
|
|
5263
|
-
}
|
|
5264
|
-
|
|
5265
5307
|
/** Update each engine object, remove destroyed objects, and update time
|
|
5266
5308
|
* @memberof Engine */
|
|
5267
5309
|
function engineObjectsUpdate()
|
|
@@ -5295,30 +5337,63 @@ function engineObjectsDestroy()
|
|
|
5295
5337
|
engineObjects = engineObjects.filter(o=>!o.destroyed);
|
|
5296
5338
|
}
|
|
5297
5339
|
|
|
5298
|
-
/**
|
|
5299
|
-
* @param {Vector2} [pos] - Center of test area
|
|
5340
|
+
/** Collects all object within a given area
|
|
5341
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
5300
5342
|
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
5301
|
-
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
5302
5343
|
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5344
|
+
* @return {Array} - List of collected objects
|
|
5303
5345
|
* @memberof Engine */
|
|
5304
|
-
function
|
|
5346
|
+
function engineObjectsCollect(pos, size, objects=engineObjects)
|
|
5305
5347
|
{
|
|
5348
|
+
const collectedObjects = [];
|
|
5306
5349
|
if (!pos) // all objects
|
|
5307
5350
|
{
|
|
5308
5351
|
for (const o of objects)
|
|
5309
|
-
|
|
5352
|
+
collectedObjects.push(o);
|
|
5310
5353
|
}
|
|
5311
|
-
else if (
|
|
5354
|
+
else if (size instanceof Vector2) // bounding box test
|
|
5312
5355
|
{
|
|
5313
5356
|
for (const o of objects)
|
|
5314
|
-
isOverlapping(pos, size, o.pos, o.size) &&
|
|
5357
|
+
isOverlapping(pos, size, o.pos, o.size) && collectedObjects.push(o);
|
|
5315
5358
|
}
|
|
5316
5359
|
else // circle test
|
|
5317
5360
|
{
|
|
5318
5361
|
const sizeSquared = size*size;
|
|
5319
5362
|
for (const o of objects)
|
|
5320
|
-
pos.distanceSquared(o.pos) < sizeSquared &&
|
|
5363
|
+
pos.distanceSquared(o.pos) < sizeSquared && collectedObjects.push(o);
|
|
5364
|
+
}
|
|
5365
|
+
return collectedObjects;
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5368
|
+
/** Triggers a callback for each object within a given area
|
|
5369
|
+
* @param {Vector2} [pos] - Center of test area, or undefined for all objects
|
|
5370
|
+
* @param {Number|Vector2} [size] - Radius of circle if float, rectangle size if Vector2
|
|
5371
|
+
* @param {Function} [callbackFunction] - Calls this function on every object that passes the test
|
|
5372
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5373
|
+
* @memberof Engine */
|
|
5374
|
+
function engineObjectsCallback(pos, size, callbackFunction, objects=engineObjects)
|
|
5375
|
+
{ engineObjectsCollect(pos, size, objects).forEach(o => callbackFunction(o)); }
|
|
5376
|
+
|
|
5377
|
+
/** Return a list of objects intersecting a ray
|
|
5378
|
+
* @param {Vector2} start
|
|
5379
|
+
* @param {Vector2} end
|
|
5380
|
+
* @param {Array} [objects=engineObjects] - List of objects to check
|
|
5381
|
+
* @return {Array} - List of objects hit
|
|
5382
|
+
* @memberof Engine */
|
|
5383
|
+
function engineObjectsRaycast(start, end, objects=engineObjects)
|
|
5384
|
+
{
|
|
5385
|
+
const hitObjects = [];
|
|
5386
|
+
for (const o of objects)
|
|
5387
|
+
{
|
|
5388
|
+
if (o.collideRaycast && isIntersecting(start, end, o.pos, o.size))
|
|
5389
|
+
{
|
|
5390
|
+
debugRaycast && debugRect(o.pos, o.size, '#f00');
|
|
5391
|
+
hitObjects.push(o);
|
|
5392
|
+
}
|
|
5321
5393
|
}
|
|
5394
|
+
|
|
5395
|
+
debugRaycast && debugLine(start, end, hitObjects.length ? '#f00' : '#00f', .02);
|
|
5396
|
+
return hitObjects;
|
|
5322
5397
|
}
|
|
5323
5398
|
|
|
5324
5399
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -5406,16 +5481,16 @@ function drawEngineSplashScreen(t)
|
|
|
5406
5481
|
rect(37,14,9,6);
|
|
5407
5482
|
|
|
5408
5483
|
// big stack
|
|
5409
|
-
rect(50,20,10,-
|
|
5410
|
-
rect(50,20,6.5,-
|
|
5411
|
-
rect(50,20,3.5,-
|
|
5412
|
-
rect(50,20,10,-
|
|
5413
|
-
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5414
|
-
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5415
|
-
circle(55,2,11.4,.5,PI-.5)
|
|
5416
|
-
rect(45,7,20,-7,color(0,2))
|
|
5417
|
-
rect(45,
|
|
5418
|
-
rect(45,
|
|
5484
|
+
rect(50,20,10,-8,color(0,1))
|
|
5485
|
+
rect(50,20,6.5,-8,color(0,2))
|
|
5486
|
+
rect(50,20,3.5,-8,color(0,3))
|
|
5487
|
+
rect(50,20,10,-8)
|
|
5488
|
+
circle(55,2,11.4,.5,PI-.5,color(3,3))
|
|
5489
|
+
circle(55,2,11.4,.5,PI/2,color(3,2),1)
|
|
5490
|
+
circle(55,2,11.4,.5,PI-.5)
|
|
5491
|
+
rect(45,7,20,-7,color(0,2))
|
|
5492
|
+
rect(45,-1,20,4,color(0,3))
|
|
5493
|
+
rect(45,-1,20,8)
|
|
5419
5494
|
|
|
5420
5495
|
// engine
|
|
5421
5496
|
for (let i=5; i--;)
|