littlejsengine 1.8.5 → 1.8.8
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/README.md +13 -3
- package/build/littlejs.d.ts +22 -15
- package/build/littlejs.esm.js +255 -55
- package/build/littlejs.esm.min.js +1 -1
- package/build/littlejs.js +253 -55
- package/build/littlejs.min.js +1 -1
- package/build/littlejs.release.js +253 -55
- package/examples/breakout/game.js +3 -0
- package/examples/breakout/index.html +3 -3
- package/examples/breakoutTutorial/index.html +2 -2
- package/examples/electron/game.js +3 -0
- package/examples/electron/index.html +2 -2
- package/examples/js13k/index.html +13 -13
- package/examples/logo.png +0 -0
- package/examples/module/game.js +3 -0
- package/examples/module/index.html +1 -1
- package/examples/particles/index.html +1 -1
- package/examples/platformer/game.js +3 -0
- package/examples/platformer/gamePlayer.js +1 -1
- package/examples/platformer/index.html +6 -6
- package/examples/puzzle/game.js +3 -0
- package/examples/puzzle/index.html +2 -2
- package/examples/starter/game.js +3 -0
- package/examples/starter/index.html +13 -13
- package/examples/stress/index.html +3 -1
- package/examples/typescript/game.js +3 -1
- package/examples/typescript/game.ts +3 -0
- package/examples/typescript/index.html +1 -1
- package/package.json +1 -1
- package/src/engine.js +186 -9
- package/src/engineAudio.js +1 -1
- package/src/engineDraw.js +1 -1
- package/src/engineExport.js +2 -0
- package/src/engineInput.js +24 -14
- package/src/engineObject.js +1 -1
- package/src/engineParticles.js +1 -1
- package/src/engineSettings.js +11 -0
- package/src/engineTileLayer.js +8 -8
- package/src/engineUtilities.js +5 -6
- package/src/engineWebGL.js +15 -14
package/build/littlejs.js
CHANGED
|
@@ -819,7 +819,7 @@ class Vector2
|
|
|
819
819
|
arrayCheck(arraySize) { return this.x >= 0 && this.y >= 0 && this.x < arraySize.x && this.y < arraySize.y; }
|
|
820
820
|
|
|
821
821
|
/** Returns this vector expressed as a string
|
|
822
|
-
* @param {
|
|
822
|
+
* @param {Number} digits - precision to display
|
|
823
823
|
* @return {String} */
|
|
824
824
|
toString(digits=3)
|
|
825
825
|
{ if (debug) { return `(${(this.x<0?'':' ') + this.x.toFixed(digits)},${(this.y<0?'':' ') + this.y.toFixed(digits)} )`; }}
|
|
@@ -1008,11 +1008,10 @@ class Color
|
|
|
1008
1008
|
* @return {Number} */
|
|
1009
1009
|
rgbaInt()
|
|
1010
1010
|
{
|
|
1011
|
-
const
|
|
1012
|
-
const
|
|
1013
|
-
const
|
|
1014
|
-
const
|
|
1015
|
-
const a = toByte(this.a)<<24;
|
|
1011
|
+
const r = clamp(this.r)*255|0;
|
|
1012
|
+
const g = clamp(this.g)*255<<8;
|
|
1013
|
+
const b = clamp(this.b)*255<<16;
|
|
1014
|
+
const a = clamp(this.a)*255<<24;
|
|
1016
1015
|
return r + g + b + a;
|
|
1017
1016
|
}
|
|
1018
1017
|
}
|
|
@@ -1120,6 +1119,12 @@ let canvasPixelated = 1;
|
|
|
1120
1119
|
* @memberof Settings */
|
|
1121
1120
|
let fontDefault = 'arial';
|
|
1122
1121
|
|
|
1122
|
+
/** Enable to show the LittleJS splash screen be shown on startup
|
|
1123
|
+
* @type {Boolean}
|
|
1124
|
+
* @default
|
|
1125
|
+
* @memberof Settings */
|
|
1126
|
+
let showSplashScreen = 0;
|
|
1127
|
+
|
|
1123
1128
|
///////////////////////////////////////////////////////////////////////////////
|
|
1124
1129
|
// WebGL settings
|
|
1125
1130
|
|
|
@@ -1353,6 +1358,11 @@ function setCanvasPixelated(pixelated) { canvasPixelated = pixelated; }
|
|
|
1353
1358
|
* @memberof Settings */
|
|
1354
1359
|
function setFontDefault(font) { fontDefault = font; }
|
|
1355
1360
|
|
|
1361
|
+
/** Set if the LittleJS splash screen be shown on startup
|
|
1362
|
+
* @param {Boolean} show
|
|
1363
|
+
* @memberof Settings */
|
|
1364
|
+
function setShowSplashScreen(show) { showSplashScreen = show; }
|
|
1365
|
+
|
|
1356
1366
|
/** Set if webgl rendering is enabled
|
|
1357
1367
|
* @param {Boolean} enable
|
|
1358
1368
|
* @memberof Settings */
|
|
@@ -1599,7 +1609,7 @@ class EngineObject
|
|
|
1599
1609
|
// init other internal object stuff
|
|
1600
1610
|
this.spawnTime = time;
|
|
1601
1611
|
this.children = [];
|
|
1602
|
-
this.collideTiles =
|
|
1612
|
+
this.collideTiles = false;
|
|
1603
1613
|
|
|
1604
1614
|
// add to list of objects
|
|
1605
1615
|
engineObjects.push(this);
|
|
@@ -1944,7 +1954,7 @@ let mainCanvasSize = vec2();
|
|
|
1944
1954
|
* @memberof Draw */
|
|
1945
1955
|
let textureInfos = [];
|
|
1946
1956
|
|
|
1947
|
-
//
|
|
1957
|
+
// Keep track of how many draw calls there were each frame for debugging
|
|
1948
1958
|
let drawCount;
|
|
1949
1959
|
|
|
1950
1960
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -2547,23 +2557,33 @@ function inputUpdatePost()
|
|
|
2547
2557
|
///////////////////////////////////////////////////////////////////////////////
|
|
2548
2558
|
// Keyboard event handlers
|
|
2549
2559
|
|
|
2550
|
-
onkeydown = (e)=>
|
|
2551
2560
|
{
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2561
|
+
onkeydown = (e)=>
|
|
2562
|
+
{
|
|
2563
|
+
if (debug && e.target != document.body) return;
|
|
2564
|
+
if (!e.repeat)
|
|
2565
|
+
{
|
|
2566
|
+
inputData[isUsingGamepad = 0][e.which] = 3;
|
|
2567
|
+
if (inputWASDEmulateDirection)
|
|
2568
|
+
inputData[0][remapKey(e.which)] = 3;
|
|
2569
|
+
}
|
|
2570
|
+
preventDefaultInput && e.preventDefault();
|
|
2571
|
+
}
|
|
2556
2572
|
|
|
2557
|
-
onkeyup = (e)=>
|
|
2558
|
-
{
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2573
|
+
onkeyup = (e)=>
|
|
2574
|
+
{
|
|
2575
|
+
if (debug && e.target != document.body) return;
|
|
2576
|
+
inputData[0][e.which] = 4;
|
|
2577
|
+
if (inputWASDEmulateDirection)
|
|
2578
|
+
inputData[0][remapKey(e.which)] = 4;
|
|
2579
|
+
}
|
|
2562
2580
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2581
|
+
// handle remapping wasd keys to directions
|
|
2582
|
+
function remapKey(c)
|
|
2583
|
+
{
|
|
2584
|
+
return inputWASDEmulateDirection ?
|
|
2585
|
+
c==87?38 : c==83?40 : c==65?37 : c==68?39 : c : c;
|
|
2586
|
+
}
|
|
2567
2587
|
}
|
|
2568
2588
|
|
|
2569
2589
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -3062,7 +3082,7 @@ class Music extends Sound
|
|
|
3062
3082
|
* @param {Boolean} [loop=1] - True if the music should loop
|
|
3063
3083
|
* @return {AudioBufferSourceNode} - The audio source node
|
|
3064
3084
|
*/
|
|
3065
|
-
|
|
3085
|
+
playMusic(volume, loop = 1)
|
|
3066
3086
|
{ return super.play(0, volume, 1, 1, loop); }
|
|
3067
3087
|
}
|
|
3068
3088
|
|
|
@@ -3709,10 +3729,10 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3709
3729
|
/** Draw directly to the 2D canvas in world space (bipass webgl)
|
|
3710
3730
|
* @param {Vector2} pos
|
|
3711
3731
|
* @param {Vector2} size
|
|
3712
|
-
* @param {Number}
|
|
3713
|
-
* @param {Boolean}
|
|
3732
|
+
* @param {Number} angle
|
|
3733
|
+
* @param {Boolean} mirror
|
|
3714
3734
|
* @param {Function} drawFunction */
|
|
3715
|
-
drawCanvas2D(pos, size, angle
|
|
3735
|
+
drawCanvas2D(pos, size, angle, mirror, drawFunction)
|
|
3716
3736
|
{
|
|
3717
3737
|
const context = this.context;
|
|
3718
3738
|
context.save();
|
|
@@ -3726,12 +3746,12 @@ constructor(pos, size=tileCollisionSize, tileInfo=tile(), scale=vec2(1), renderO
|
|
|
3726
3746
|
}
|
|
3727
3747
|
|
|
3728
3748
|
/** Draw a tile directly onto the layer canvas
|
|
3729
|
-
* @param {Vector2}
|
|
3730
|
-
* @param {Vector2}
|
|
3749
|
+
* @param {Vector2} pos
|
|
3750
|
+
* @param {Vector2} [size=Vector2(1,1)]
|
|
3731
3751
|
* @param {TileInfo} [tileInfo]
|
|
3732
|
-
* @param {Color}
|
|
3733
|
-
* @param {Number}
|
|
3734
|
-
* @param {Boolean}
|
|
3752
|
+
* @param {Color} [color=Color()]
|
|
3753
|
+
* @param {Number} [angle=0]
|
|
3754
|
+
* @param {Boolean} [mirror=0] */
|
|
3735
3755
|
drawTile(pos, size=vec2(1), tileInfo, color=new Color, angle, mirror)
|
|
3736
3756
|
{
|
|
3737
3757
|
this.drawCanvas2D(pos, size, angle, mirror, (context)=>
|
|
@@ -3894,7 +3914,7 @@ class ParticleEmitter extends EngineObject
|
|
|
3894
3914
|
this.fadeRate = fadeRate;
|
|
3895
3915
|
/** @property {Number} - Apply extra randomness percent */
|
|
3896
3916
|
this.randomness = randomness;
|
|
3897
|
-
/** @property {
|
|
3917
|
+
/** @property {Boolean} - Do particles collide against tiles */
|
|
3898
3918
|
this.collideTiles = collideTiles;
|
|
3899
3919
|
/** @property {Number} - Should particles use addtive blend */
|
|
3900
3920
|
this.additive = additive;
|
|
@@ -4382,7 +4402,7 @@ let glCanvas;
|
|
|
4382
4402
|
let glContext;
|
|
4383
4403
|
|
|
4384
4404
|
// WebGL internal variables not exposed to documentation
|
|
4385
|
-
let glActiveTexture, glShader, glArrayBuffer, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
4405
|
+
let glActiveTexture, glShader, glArrayBuffer, glVertexData, glPositionData, glColorData, glBatchCount, glBatchAdditive, glAdditive;
|
|
4386
4406
|
|
|
4387
4407
|
///////////////////////////////////////////////////////////////////////////////
|
|
4388
4408
|
|
|
@@ -4419,10 +4439,10 @@ function glInit()
|
|
|
4419
4439
|
);
|
|
4420
4440
|
|
|
4421
4441
|
// init buffers
|
|
4422
|
-
|
|
4442
|
+
glVertexData = new ArrayBuffer(gl_VERTEX_BUFFER_SIZE);
|
|
4443
|
+
glPositionData = new Float32Array(glVertexData);
|
|
4444
|
+
glColorData = new Uint32Array(glVertexData);
|
|
4423
4445
|
glArrayBuffer = glContext.createBuffer();
|
|
4424
|
-
glPositionData = new Float32Array(vertexData);
|
|
4425
|
-
glColorData = new Uint32Array(vertexData);
|
|
4426
4446
|
glBatchCount = 0;
|
|
4427
4447
|
}
|
|
4428
4448
|
|
|
@@ -4450,7 +4470,7 @@ function glPreRender()
|
|
|
4450
4470
|
glContext.vertexAttribPointer(location, size, type, normalize, gl_VERTEX_BYTE_STRIDE, offset);
|
|
4451
4471
|
offset += size*typeSize;
|
|
4452
4472
|
}
|
|
4453
|
-
initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
|
|
4473
|
+
initVertexAttribArray('p', gl_FLOAT, 4, 4); // position & texture
|
|
4454
4474
|
initVertexAttribArray('c', gl_UNSIGNED_BYTE, 1, 4, 1); // color
|
|
4455
4475
|
initVertexAttribArray('a', gl_UNSIGNED_BYTE, 1, 4, 1); // additiveColor
|
|
4456
4476
|
|
|
@@ -4520,7 +4540,7 @@ function glCreateProgram(vsSource, fsSource)
|
|
|
4520
4540
|
return program;
|
|
4521
4541
|
}
|
|
4522
4542
|
|
|
4523
|
-
/** Create WebGL texture from an image and
|
|
4543
|
+
/** Create WebGL texture from an image and init the texture settings
|
|
4524
4544
|
* @param {Image} image
|
|
4525
4545
|
* @return {WebGLTexture}
|
|
4526
4546
|
* @memberof WebGL */
|
|
@@ -4529,7 +4549,8 @@ function glCreateTexture(image)
|
|
|
4529
4549
|
// build the texture
|
|
4530
4550
|
const texture = glContext.createTexture();
|
|
4531
4551
|
glContext.bindTexture(gl_TEXTURE_2D, texture);
|
|
4532
|
-
|
|
4552
|
+
if (image)
|
|
4553
|
+
glContext.texImage2D(gl_TEXTURE_2D, 0, gl_RGBA, gl_RGBA, gl_UNSIGNED_BYTE, image);
|
|
4533
4554
|
|
|
4534
4555
|
// use point filtering for pixelated rendering
|
|
4535
4556
|
const filter = canvasPixelated ? gl_NEAREST : gl_LINEAR;
|
|
@@ -4537,6 +4558,7 @@ function glCreateTexture(image)
|
|
|
4537
4558
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, filter);
|
|
4538
4559
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_S, gl_CLAMP_TO_EDGE);
|
|
4539
4560
|
glContext.texParameteri(gl_TEXTURE_2D, gl_TEXTURE_WRAP_T, gl_CLAMP_TO_EDGE);
|
|
4561
|
+
|
|
4540
4562
|
return texture;
|
|
4541
4563
|
}
|
|
4542
4564
|
|
|
@@ -4551,8 +4573,7 @@ function glFlush()
|
|
|
4551
4573
|
glContext.enable(gl_BLEND);
|
|
4552
4574
|
|
|
4553
4575
|
// draw all the sprites in the batch and reset the buffer
|
|
4554
|
-
glContext.bufferSubData(gl_ARRAY_BUFFER, 0,
|
|
4555
|
-
glPositionData.subarray(0, glBatchCount * gl_INDICIES_PER_VERT));
|
|
4576
|
+
glContext.bufferSubData(gl_ARRAY_BUFFER, 0, glVertexData);
|
|
4556
4577
|
glContext.drawArrays(gl_TRIANGLE_STRIP, 0, glBatchCount);
|
|
4557
4578
|
glBatchCount = 0;
|
|
4558
4579
|
glBatchAdditive = glAdditive;
|
|
@@ -4607,11 +4628,11 @@ function glDraw(x, y, sizeX, sizeY, angle, uv0X, uv0Y, uv1X, uv1Y, rgba, rgbaAdd
|
|
|
4607
4628
|
// setup 2 triangle strip quad
|
|
4608
4629
|
for(let i = vertCount, offset = glBatchCount * gl_INDICIES_PER_VERT; i--;)
|
|
4609
4630
|
{
|
|
4610
|
-
|
|
4611
|
-
glPositionData[offset++] = positionData[j
|
|
4612
|
-
glPositionData[offset++] = positionData[j
|
|
4613
|
-
glPositionData[offset++] = positionData[j
|
|
4614
|
-
glPositionData[offset++] = positionData[j
|
|
4631
|
+
const j = clamp(i-1, 0, 3)*4; // degenerate tri at ends
|
|
4632
|
+
glPositionData[offset++] = positionData[j+0];
|
|
4633
|
+
glPositionData[offset++] = positionData[j+1];
|
|
4634
|
+
glPositionData[offset++] = positionData[j+2];
|
|
4635
|
+
glPositionData[offset++] = positionData[j+3];
|
|
4615
4636
|
glColorData[offset++] = rgba;
|
|
4616
4637
|
glColorData[offset++] = rgbaAdditive;
|
|
4617
4638
|
}
|
|
@@ -4812,7 +4833,7 @@ const engineName = 'LittleJS';
|
|
|
4812
4833
|
* @type {String}
|
|
4813
4834
|
* @default
|
|
4814
4835
|
* @memberof Engine */
|
|
4815
|
-
const engineVersion = '1.8.
|
|
4836
|
+
const engineVersion = '1.8.8';
|
|
4816
4837
|
|
|
4817
4838
|
/** Frames per second to update objects
|
|
4818
4839
|
* @type {Number}
|
|
@@ -4995,7 +5016,7 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
4995
5016
|
}
|
|
4996
5017
|
|
|
4997
5018
|
// setup html
|
|
4998
|
-
|
|
5019
|
+
const styleBody =
|
|
4999
5020
|
'margin:0;overflow:hidden;' + // fill the window
|
|
5000
5021
|
'background:#000;' + // set background color
|
|
5001
5022
|
'touch-action:none;' + // prevent mobile pinch to resize
|
|
@@ -5016,14 +5037,13 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5016
5037
|
|
|
5017
5038
|
// set canvas style
|
|
5018
5039
|
const styleCanvas =
|
|
5019
|
-
'position:absolute;' +
|
|
5020
|
-
'top:50%;left:50%;transform:translate(-50%,-50%);
|
|
5021
|
-
(canvasPixelated?'image-rendering:pixelated':''); // pixelated rendering
|
|
5040
|
+
'position:absolute;' + // position
|
|
5041
|
+
'top:50%;left:50%;transform:translate(-50%,-50%)'; // center
|
|
5022
5042
|
(glCanvas||mainCanvas).style = mainCanvas.style = overlayCanvas.style = styleCanvas;
|
|
5023
5043
|
|
|
5024
|
-
//
|
|
5025
|
-
|
|
5026
|
-
new Promise(
|
|
5044
|
+
// create promises for loading images
|
|
5045
|
+
const promises = imageSources.map((src, textureIndex)=>
|
|
5046
|
+
new Promise(resolve =>
|
|
5027
5047
|
{
|
|
5028
5048
|
const image = new Image;
|
|
5029
5049
|
image.onerror = image.onload = ()=>
|
|
@@ -5033,7 +5053,24 @@ function engineInit(gameInit, gameUpdate, gameUpdatePost, gameRender, gameRender
|
|
|
5033
5053
|
}
|
|
5034
5054
|
image.src = src;
|
|
5035
5055
|
})
|
|
5036
|
-
)
|
|
5056
|
+
)
|
|
5057
|
+
|
|
5058
|
+
// draw splash screen
|
|
5059
|
+
showSplashScreen && promises.push(new Promise(resolve =>
|
|
5060
|
+
{
|
|
5061
|
+
let t = 0;
|
|
5062
|
+
console.log(`LittleJS Engine v${engineVersion}`);
|
|
5063
|
+
updateSplash();
|
|
5064
|
+
function updateSplash()
|
|
5065
|
+
{
|
|
5066
|
+
clearInput();
|
|
5067
|
+
drawEngineSplashScreen(t+=.01);
|
|
5068
|
+
t>1 ? resolve() : setTimeout(updateSplash,16);
|
|
5069
|
+
}
|
|
5070
|
+
}));
|
|
5071
|
+
|
|
5072
|
+
// load all of the images
|
|
5073
|
+
Promise.all(promises).then(()=>
|
|
5037
5074
|
{
|
|
5038
5075
|
// start the engine
|
|
5039
5076
|
gameInit();
|
|
@@ -5111,4 +5148,165 @@ function engineObjectsCallback(pos, size, callbackFunction, objects=engineObject
|
|
|
5111
5148
|
for (const o of objects)
|
|
5112
5149
|
pos.distanceSquared(o.pos) < sizeSquared && callbackFunction(o);
|
|
5113
5150
|
}
|
|
5151
|
+
}
|
|
5152
|
+
|
|
5153
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
5154
|
+
// LittleJS splash screen and logo
|
|
5155
|
+
|
|
5156
|
+
function drawEngineSplashScreen(t)
|
|
5157
|
+
{
|
|
5158
|
+
// background
|
|
5159
|
+
const grayscale = 0;
|
|
5160
|
+
const x = mainContext;
|
|
5161
|
+
const w = mainCanvas.width = innerWidth;
|
|
5162
|
+
const h = mainCanvas.height = innerHeight;
|
|
5163
|
+
const p3 = percent(t, 1, .8);
|
|
5164
|
+
const p4 = percent(t, 0, .5);
|
|
5165
|
+
const g = x.createRadialGradient(w/2,h/2,0,w/2,h/2,Math.hypot(w,h)*.7);
|
|
5166
|
+
g.addColorStop(0,hsl(0,0,lerp(p4,0,p3/2),p3));
|
|
5167
|
+
g.addColorStop(1,hsl(0,0,0,p3));
|
|
5168
|
+
x.save();
|
|
5169
|
+
x.fillStyle = g;
|
|
5170
|
+
x.fillRect(0,0,w,h);
|
|
5171
|
+
|
|
5172
|
+
// logo - fade in and out
|
|
5173
|
+
const rect = (X, Y, W, H, C)=>
|
|
5174
|
+
{
|
|
5175
|
+
x.beginPath();
|
|
5176
|
+
x.rect(X,Y,W,C?H*p:H);
|
|
5177
|
+
x.fillStyle = C;
|
|
5178
|
+
C ? x.fill() : x.stroke();
|
|
5179
|
+
};
|
|
5180
|
+
const line = (X, Y, Z, W)=>
|
|
5181
|
+
{
|
|
5182
|
+
x.beginPath();
|
|
5183
|
+
x.lineTo(X,Y);
|
|
5184
|
+
x.lineTo(Z,W);
|
|
5185
|
+
x.stroke();
|
|
5186
|
+
};
|
|
5187
|
+
const circle = (X, Y, R, A=0, B=2*PI, C, F)=>
|
|
5188
|
+
{
|
|
5189
|
+
const D = (A+B)/2, E = p*(B-A)/2;
|
|
5190
|
+
x.beginPath();
|
|
5191
|
+
F && x.lineTo(X,Y);
|
|
5192
|
+
x.arc(X,Y,R,D-E,D+E);
|
|
5193
|
+
x.fillStyle = C;
|
|
5194
|
+
C ? x.fill() : x.stroke();
|
|
5195
|
+
};
|
|
5196
|
+
const color = (c=0, l=0) =>
|
|
5197
|
+
hsl([.98,.3,.57,.14][c%4]-10,grayscale?0:.8,[0,.3,.5,.8,.9][l]);
|
|
5198
|
+
const alpha = wave(1,1,t);
|
|
5199
|
+
const p = percent(alpha, .1, .5);
|
|
5200
|
+
|
|
5201
|
+
// setup
|
|
5202
|
+
x.translate(w/2,h/2);
|
|
5203
|
+
const size = min(6, min(w,h)/99); // fit to screen
|
|
5204
|
+
x.scale(size,size);
|
|
5205
|
+
x.translate(-40,-35);
|
|
5206
|
+
x.lineJoin = x.lineCap = 'round';
|
|
5207
|
+
x.lineWidth = 1+p;
|
|
5208
|
+
|
|
5209
|
+
// drawing effect
|
|
5210
|
+
const p2 = percent(alpha,.1,1);
|
|
5211
|
+
x.setLineDash([99*p2,99]);
|
|
5212
|
+
|
|
5213
|
+
// cab top
|
|
5214
|
+
rect(7,17,18,-8,color(2,2));
|
|
5215
|
+
rect(7,9,18,4,color(2,3));
|
|
5216
|
+
rect(25,9,8,8,color(2,1));
|
|
5217
|
+
rect(25,9,-18,8);
|
|
5218
|
+
rect(25,9,8,8);
|
|
5219
|
+
|
|
5220
|
+
// cab
|
|
5221
|
+
rect(25,17,7,22,color());
|
|
5222
|
+
rect(11,40,14,-23,color(1,1));
|
|
5223
|
+
rect(11,17,14,17,color(1,2));
|
|
5224
|
+
rect(11,17,14,9,color(1,3));
|
|
5225
|
+
rect(15,31,6,-9,color(2,2));
|
|
5226
|
+
circle(15,23,5,0,PI/2,color(2,4),1);
|
|
5227
|
+
rect(25,17,-14,23);
|
|
5228
|
+
rect(21,22,-6,9);
|
|
5229
|
+
|
|
5230
|
+
// little stack
|
|
5231
|
+
rect(37,14,9,6,color(3,2));
|
|
5232
|
+
rect(37,14,4,6,color(3,3));
|
|
5233
|
+
rect(37,14,9,6);
|
|
5234
|
+
|
|
5235
|
+
// big stack
|
|
5236
|
+
rect(50,20,10,-10,color(0,1));
|
|
5237
|
+
rect(50,20,6,-10,color(0,2));
|
|
5238
|
+
rect(50,20,3,-10,color(0,3));
|
|
5239
|
+
rect(50,10,10,10);
|
|
5240
|
+
circle(55,2,11,.5,PI-.5,color(3,3));
|
|
5241
|
+
circle(55,2,11,.5,PI/2,color(3,2),1);
|
|
5242
|
+
circle(55,2,11,.5,PI-.5);
|
|
5243
|
+
rect(45,7,20,-7,color(0,2));
|
|
5244
|
+
rect(45,0,20,3,color(0,3));
|
|
5245
|
+
rect(45,0,20,7);
|
|
5246
|
+
|
|
5247
|
+
// engine
|
|
5248
|
+
for (let i=5; i--;)
|
|
5249
|
+
{
|
|
5250
|
+
// stagger radius to fix slight seam
|
|
5251
|
+
circle(60-i*6,30, 9.9,0,2*PI,color(i+2,3));
|
|
5252
|
+
circle(60-i*6,30,10.0,-.5,PI+.5,color(i+2,2));
|
|
5253
|
+
circle(60-i*6,30,10.1,.5,PI-.5,color(i+2,1));
|
|
5254
|
+
}
|
|
5255
|
+
|
|
5256
|
+
// engine outline
|
|
5257
|
+
circle(36,30,10,PI/2,PI*3/2);
|
|
5258
|
+
circle(47,30,10,PI/2,PI*3/2);
|
|
5259
|
+
circle(60,30,10);
|
|
5260
|
+
line(36,20,60,20);
|
|
5261
|
+
|
|
5262
|
+
// engine front light
|
|
5263
|
+
circle(60,30,4,PI,3*PI,color(3,2));
|
|
5264
|
+
circle(60,30,4,PI,2*PI,color(3,3));
|
|
5265
|
+
circle(60,30,4,PI,3*PI);
|
|
5266
|
+
|
|
5267
|
+
// front brush
|
|
5268
|
+
for (let i=6; i--;)
|
|
5269
|
+
{
|
|
5270
|
+
x.beginPath();
|
|
5271
|
+
x.lineTo(53,54);
|
|
5272
|
+
x.lineTo(53,40);
|
|
5273
|
+
x.lineTo(53+(1+i*2.9)*p,40);
|
|
5274
|
+
x.lineTo(53+(4+i*3.5)*p,54);
|
|
5275
|
+
x.fillStyle = color(0,i%2+2);
|
|
5276
|
+
x.fill() || i%2 && x.stroke();
|
|
5277
|
+
}
|
|
5278
|
+
|
|
5279
|
+
// wheels
|
|
5280
|
+
rect(5,40,9,6,color());
|
|
5281
|
+
rect(15,54,38,-14,color())
|
|
5282
|
+
for (let i=3; i--;)
|
|
5283
|
+
for (let j=2; j--;)
|
|
5284
|
+
{
|
|
5285
|
+
circle(15*i+15,47,j?7:1,PI,3*PI,color(i,3));
|
|
5286
|
+
x.stroke();
|
|
5287
|
+
circle(15*i+15,47,j?7:1,0,PI,color(i,2));
|
|
5288
|
+
x.stroke();
|
|
5289
|
+
}
|
|
5290
|
+
line(6,40,68,40) // center
|
|
5291
|
+
line(77,54,4,54) // bottom
|
|
5292
|
+
|
|
5293
|
+
// text
|
|
5294
|
+
const s = engineName;
|
|
5295
|
+
x.font = '900 16px arial';
|
|
5296
|
+
x.textAlign = 'center';
|
|
5297
|
+
x.textBaseline = 'top';
|
|
5298
|
+
x.lineWidth = 1+p*3
|
|
5299
|
+
let w2 = 0;
|
|
5300
|
+
for (let i=0; i<s.length; ++i)
|
|
5301
|
+
w2 += x.measureText(s[i]).width;
|
|
5302
|
+
for (let j=2; j--;)
|
|
5303
|
+
for (let i=0, X=41-w2/2; i<s.length; ++i)
|
|
5304
|
+
{
|
|
5305
|
+
x.fillStyle = color(i,grayscale?3:2);
|
|
5306
|
+
const w = x.measureText(s[i]).width;
|
|
5307
|
+
x[j?'strokeText':'fillText'](s[i],X+w/2,55.5,17*p);
|
|
5308
|
+
X += w;
|
|
5309
|
+
}
|
|
5310
|
+
|
|
5311
|
+
x.restore();
|
|
5114
5312
|
}
|