const AU = 149597870700; const CANVAS_WIDTH = 1000; const CANVAS_HEIGHT = 800; const ASPECT_RATIO = CANVAS_WIDTH / CANVAS_HEIGHT; const CANVAS_WIDTH_REAL = 0.01*AU; const FOLLOWED_OBJ = 1; const MAX_TIME_STEP = 1e3; const LABELS_ON = true; function wrap(x, min_x, max_x) { if(x < 0) { x = abs(x); x = ((x-min_x) % (max_x-min_x)) + min_x; return max_x - x + 1; } else return ((x-min_x) % (max_x-min_x)) + min_x; } class Camera { constructor() { this.position = new vec2(); this.followedObject = null; } follow(obj) { this.followedObject = obj; } tick() { if(this.followedObject !== null) this.position = this.followedObject.position.copy(); } } class BgStar { constructor() { this.position = new vec3( random()*CANVAS_WIDTH, random()*CANVAS_HEIGHT, random()*100 + 60); this.radius = 60/this.position.z + 0.5; this.color = `rgba(255, 255, 255, ${ 1 / sqrt(this.position.z) })`; } render(camera) { let xCanvas = map( camera.position.x, -CANVAS_WIDTH_REAL/2, CANVAS_WIDTH_REAL/2, 0, CANVAS_WIDTH - 1); let yCanvas = map( camera.position.y, -CANVAS_WIDTH_REAL/2/ASPECT_RATIO, CANVAS_WIDTH_REAL/2/ASPECT_RATIO, CANVAS_HEIGHT - 1, 0); xCanvas = wrap( this.position.x - xCanvas/this.position.z, 0, CANVAS_WIDTH); yCanvas = wrap( this.position.y - yCanvas/this.position.z, 0, CANVAS_HEIGHT); fillStyle(this.color); beginPath(); circle(xCanvas, yCanvas, this.radius); fill(); } } class StarBackground { constructor(density) { this.stars = []; for(let i = 0; i < density; ++i) this.stars.push(new BgStar()); } render(camera) { this.stars.forEach(star => star.render(camera)); } } class SpaceObject { constructor(label, props) { this.label = label; this.position = new vec2(); this.velocity = new vec2(); this.maxSpeed = Infinity; this.acceleration = new vec2(); this.mass = 1; this.volume = 1; this.color = "#FFFFFF"; if(typeof props === "object") { if(props.position !== undefined) this.position = props.position; if(props.velocity !== undefined) this.velocity = props.velocity; if(props.maxSpeed !== undefined) this.maxSpeed = props.maxSpeed; if(props.acceleration !== undefined) this.acceleration = props.acceleration; if(props.mass !== undefined) this.mass = props.mass; if(props.volume !== undefined) this.volume = props.volume; if(props.color !== undefined) this.color = props.color; } } applyForce(f) { this.acceleration.add(vec2.scaled(f, 1/this.mass)); } tick(dt) { this.velocity.add(vec2.scaled(this.acceleration, dt)); if(this.velocity.magnitude2() > this.maxSpeed*this.maxSpeed) this.velocity.normalize().scale(this.maxSpeed); this.position.add(vec2.scaled(this.velocity, dt)); this.acceleration.scale(0); } render(camera) { const radius = sqrt(abs(this.volume / PI)); const radiusCanvas = map( radius, 0, CANVAS_WIDTH_REAL, 0, CANVAS_WIDTH); const xCanvas = map( this.position.x - camera.position.x, -CANVAS_WIDTH_REAL/2, CANVAS_WIDTH_REAL/2, 0, CANVAS_WIDTH - 1); const yCanvas = map( this.position.y - camera.position.y, -CANVAS_WIDTH_REAL/2/ASPECT_RATIO, CANVAS_WIDTH_REAL/2/ASPECT_RATIO, CANVAS_HEIGHT - 1, 0); fillStyle(this.color); beginPath(); circle(xCanvas, yCanvas, radiusCanvas); fill(); if(LABELS_ON) { font("normal 15px serif"); textAlign("center"); fillText(this.label, xCanvas, yCanvas + radiusCanvas + 20); } } } class Space { constructor() { this.objects = []; } add(x) { this.objects.push(x); } tick(dt) { for(let i = 0; i < this.objects.length-1; ++i) { let a = this.objects[i]; for(let j = i+1; j < this.objects.length; ++j) { let b = this.objects[j]; let forces = Space.computeGravitationalForces(a, b); a.applyForce(forces.a); b.applyForce(forces.b); } } this.objects.forEach(obj => obj.tick(dt)); } render(camera) { this.objects.forEach(obj => obj.render(camera)); } static computeGravitationalForces(a, b) { const G = 6.67408e-11; const r = vec2.subtract(a.position, b.position); const rmag2 = r.magnitude2(); if(rmag2 == 0) // division by zero -> apply no force return { a: new vec2(), b: new vec2() }; const fg_ab = G*a.mass*b.mass/rmag2; const r0 = vec2.normalized(r); const Fg_b = vec2.scaled(r0, fg_ab); const Fg_a = vec2.scaled(Fg_b, -1); return { a: Fg_a, b: Fg_b }; } } createCanvas(CANVAS_WIDTH, CANVAS_HEIGHT); const sSpeed = createLabeledSlider( "Exponent rychlosti času:", 4, 10, 0.1, 4, true); const pSpeed = createParagraph("Čas zrychlený 10000×"); let background = new StarBackground(1000); let space = new Space(); space.add(new SpaceObject("Slunce", { color: "#fcba03", volume: 1523324963020555013, mass: 1.989e+30, position: new vec2(0, 0), velocity: vec2.fromPolar(1000*1, -PI/3) })); space.add(new SpaceObject("Země", { color: "#108f18", volume: 127796483130631, mass: 5.972e+24, position: new vec2(-147091144000, 0), velocity: vec2.fromPolar(30290, PI/2) })); space.add(new SpaceObject("Měsíc", { color: "#eeeeee", volume: 9479807385742, mass: 7.347673e+22, position: new vec2(-147091144000-384400000, 0), velocity: vec2.fromPolar(31290, PI/2) })); let camera = new Camera(); camera.follow(space.objects[FOLLOWED_OBJ]); let timeSpeed = 1e4; let lastTime = 0; sSpeed.onInput(() => { timeSpeed = pow(10, sSpeed.value()); pSpeed.text(`Čas zrychlený ${floor(timeSpeed)}×`); }); function loop(time) { if(time - lastTime > 100) // tab focus lost (=> time freezes) lastTime = time; let dt = (time - lastTime) / 1000 * timeSpeed; lastTime = time; let i = 0; while(dt-MAX_TIME_STEP > 0) { dt -= MAX_TIME_STEP; space.tick(MAX_TIME_STEP); ++i; } space.tick(dt); camera.tick(); clear(0, 0, 0); background.render(camera); space.render(camera); window.requestAnimationFrame(loop); } window.requestAnimationFrame(loop);
Editor je nyní spuštěn v režimu pouze pro čtení. Scripty můžete s příslušným oprávněním vytvářet a editovat z  uživatelské sekce.