]> git.r.bdr.sh - rbdr/sumo/blob - lib/sumo.js
d79826f95d57bda94f42cef92acae10965761ad6
[rbdr/sumo] / lib / sumo.js
1 import PhysicsWorldControlSystem from './systems/physics_world_control';
2 import PhysicsToAttributesSystem from './systems/physics_to_attributes';
3 import CreateCouplingLineSystem from './systems/create_coupling_line';
4 import RenderSystem from './systems/render';
5 import AttributesToRenderableSystem from './systems/attributes_to_renderable';
6 import SumoFactory from './factories/sumo';
7 import Serpentity from '@serpentity/serpentity';
8 import { Application } from 'pixi.js';
9 import { Engine } from 'matter-js';
10
11 /* global window document */
12
13 const internals = {
14 kBackgroundColor: 0xd8c590,
15 kNoElementError: 'No element found. Cannot render.',
16
17 // Handler for the window load event. Initializes and runs the app.
18
19 onLoad() {
20
21 const sumo = new internals.Sumo({
22 element: document.getElementById('sumo-app-entry-point')
23 });
24
25 sumo.startLoop();
26
27 internals.exports.sumo = sumo;
28 }
29 };
30
31 /**
32 * Sumo - main entry point. Attached to window->load
33 *
34 * @class Sumo
35 *
36 * @param {object} config the configuration to extend the object
37 *
38 * @property {HTMLElement} [element=null] the element in which to render.
39 * Required, will throw if not provided
40 * @property {Number} [fps=60] the fps target to maintain
41 * @property {Number} [verticalResolution=224] how many pixels to render in the vertical
42 * axis (gets scaled if the canvas is larger)
43 * @property {Array<Number>} [aspectRatio=[2.76, 1]] the aspect ratio experssed as
44 * an array of two numbers, where aspect ratio x:y is [x, y] (eg. [16, 9])
45 */
46
47 internals.Sumo = class Sumo {
48
49 constructor(config) {
50
51 this.fps = 60;
52 this.aspectRatio = [2.76, 1];
53 this.verticalResolution = 224;
54
55 Object.assign(this, config);
56
57 if (!this.element) {
58 throw new Error(internals.kNoElementError);
59 }
60
61 this._engine = new Serpentity();
62
63 this._previousTime = 0;
64 this._looping = false;
65
66 // Initialization functions
67 this._initializeCanvas();
68 this._initializeMatter();
69 this._initializePixi();
70 this._initializeSystems();
71 this._initializeEntities();
72 }
73
74 /**
75 * Starts the main loop. Resets the FPS (if you change it it won't go
76 * live until after you stop and start the loop)
77 *
78 * @function startLoop
79 * @instance
80 * @memberof Sumo
81 */
82 startLoop() {
83
84 this._looping = true;
85 this._frameDuration = 1000 / this.fps;
86 window.requestAnimationFrame(this._loop.bind(this));
87 }
88
89 /**
90 * Pauses the loop
91 *
92 * @function pauseLoop
93 * @instance
94 * @memberof Sumo
95 */
96 pauseLoop() {
97
98 this._looping = false;
99 }
100
101 // The main loop used above. Runs the serpentity update process and
102 // attempts to maintain FPS. The rest is handled by the engine.
103
104 _loop(currentTime) {
105
106 if (!this._looping) {
107 return;
108 }
109
110 window.requestAnimationFrame(this._loop.bind(this));
111
112 const currentFrameDuration = currentTime - this._previousTime;
113
114 if (currentFrameDuration > this._frameDuration) {
115
116 // We're sending the currentTime since it gives better results for
117 // this type of renderer, though usually we expect the delta
118 this._engine.update(currentTime);
119 this._previousTime = currentTime;
120 }
121 }
122
123 // Creates a canvas for rendering
124
125 _initializeCanvas() {
126
127 this._canvas = document.createElement('canvas');
128 this.element.appendChild(this._canvas);
129 this._resizeCanvas();
130 window.addEventListener('resize', this._resizeCanvas.bind(this));
131 }
132
133 // Initialize MatterJs
134
135 _initializeMatter() {
136
137 this._matterJs = Engine.create();
138
139 this._matterJs.world.gravity.y = 0;
140 }
141
142 // Initialize Pixi
143
144 _initializePixi() {
145
146 this._pixi = new Application({
147 backgroundColor: internals.kBackgroundColor,
148 view: this._canvas,
149 width: this._canvas.width,
150 height: this._canvas.height
151 });
152 }
153
154 // Resizes the canvas to a square the size of the smallest magnitude
155 // of the window.
156
157 _resizeCanvas() {
158
159 let width = window.innerWidth;
160 let height = Math.round(width * this.aspectRatio[1] / this.aspectRatio[0]);
161
162 if (window.innerHeight < height) {
163 height = window.innerHeight;
164 width = Math.round(height * this.aspectRatio[0] / this.aspectRatio[1]);
165 }
166
167 this._canvas.style.width = `${width}px`;
168 this._canvas.style.height = `${height}px`;
169
170 this._canvas.width = Math.round(this.verticalResolution * this.aspectRatio[0] / this.aspectRatio[1]);
171 this._canvas.height = this.verticalResolution;
172 }
173
174 // Initializes the serpentity systems
175
176 _initializeSystems() {
177
178 this._engine.addSystem(new PhysicsWorldControlSystem({
179 engine: this._matterJs
180 }));
181
182 this._engine.addSystem(new PhysicsToAttributesSystem());
183
184 this._engine.addSystem(new AttributesToRenderableSystem());
185
186 this._engine.addSystem(new CreateCouplingLineSystem());
187
188 this._engine.addSystem(new RenderSystem({
189 application: this._pixi
190 }));
191 }
192
193 // Initializes the serpentity entities
194
195 _initializeEntities() {
196
197 const entityA = SumoFactory.createSumo(null, {
198 position: {
199 x: 50,
200 y: 50
201 }
202 });
203
204 const entityB = SumoFactory.createSumo(null, {
205 position: {
206 x: 309,
207 y: 112
208 }
209 });
210
211 const entityC = SumoFactory.createSumo(null, {
212 position: {
213 x: 500,
214 y: 78
215 }
216 });
217
218 SumoFactory.createRubberBand(this._engine, {
219 entityA,
220 entityB
221 });
222
223 SumoFactory.createRubberBand(this._engine, {
224 entityA: entityC,
225 entityB
226 });
227
228 // To keep the coupling behind, we'll manually add the sumos later
229
230 this._engine.addEntity(entityA);
231 this._engine.addEntity(entityB);
232 this._engine.addEntity(entityC);
233 }
234 };
235
236 export default internals.exports = {};
237
238 // autorun.bat
239 window.addEventListener('load', internals.onLoad);