diff --git a/public/img/parabola.svg b/public/img/parabola.svg deleted file mode 100644 index 8e81a06..0000000 --- a/public/img/parabola.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - diff --git a/src/frontend/simulations/parabola.nim b/src/frontend/simulations/parabola.nim index c616575..d32ec38 100644 --- a/src/frontend/simulations/parabola.nim +++ b/src/frontend/simulations/parabola.nim @@ -5,34 +5,32 @@ import matter, utils type Exercise = object - pos: tuple[x, y: int] - angle: int # Degrees - angleRad: float - speed: int - velocity: tuple[x, y: float] + pos*: tuple[x, y: int] + angle*: int # Degrees + angleRad*: float + speed*: int + velocity*: tuple[x, y: float] + text*: string ExerciseStatus = enum esStart # Not launched yet esStarted # In the air esEnd # Touched something and stopped -proc initExercise(pos: tuple[x, y: int], angle: int, speed: int): Exercise = +proc initExercise(pos: tuple[x, y: int], angle: int, speed: int, text = ""): Exercise = let angleRad = degToRad(float angle) - Exercise(pos: pos, angle: angle, angleRad: angleRad, speed: speed, velocity: speedToVelRad(float speed, angleRad)) + Exercise(pos: pos, angle: angle, angleRad: angleRad, speed: speed, velocity: speedToVelRad(float speed, angleRad), text: text) const deltaTime = 1000 / 60 # 60fps, 60 times in one second (1000 milliseconds) secPerFrame = 1 / 60 canvasWidth = 700 canvasHeight = 500 - floorHeight = 20 - timeScale = 0.65 - -let - wrapObject = JsObject{min: JsObject{x: 0, y: undefined}, max: JsObject{x: canvasWidth, y: undefined}} # To avoid boilerplate + groundHeight = 20 + timeScale = 0.60 var - engine*, mrender*, bullet, floor, ground*, runner*: JsObject + engine*, mrender*, bullet, ground*, runner*, thingy*: JsObject constraint*, mconstraint*, mouse*: JsObject canvas*: Element @@ -40,12 +38,29 @@ var mConstraintDragEnded*: bool # True when you release the mouse constraint # The first exercise is the default exercise and it is modifed as you modify the bullet - exercises = @[initExercise(pos = (canvasWidth div 2, 0), angle = 0, speed = 20), initExercise(pos = (50, 0), angle = 48, speed = 30)] + exercises = @[ + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (0, 0), angle = 0, speed = 20), + initExercise(pos = (50, 0), angle = 48, speed = 30, text = + "Una pelota se lanza a 12 m/s y a un ángulo de 67° " & + "respecto a la horizontal. ¿Cuál es su altura a los 2 segundos?" + ) + ] curExercise = 0 exerciseStatus: ExerciseStatus exerciseTotalTime: float paused = false # Is engine.timing.timeScale == 0? + +proc wrapObject(): JsObject = + JsObject{min: JsObject{x: 0, y: undefined}, max: JsObject{x: canvas.clientWidth, y: undefined}} # To avoid boilerplate + ## Loads the simulation proc load*() = # Render all MathJax expressions asynchronously @@ -61,10 +76,10 @@ proc load*() = canvas: canvas, engine: engine, options: JsObject{ - width: canvasWidth, - height: canvasHeight, + width: canvas.clientWidth, + height: canvas.clientHeight, showAngleIndicator: false, - background: "rgb(20, 21, 31)", + background: "transparent",#"rgb(20, 21, 31)", } }) Render.run(mrender) @@ -73,14 +88,19 @@ proc load*() = Runner.run(runner, engine) # Create and add all bodies to the world - bullet = Bodies.circle(300, 300, 25, JsObject{isStatic: false, frictionAir: 0, friction: 1, plugin: JsObject{wrap: wrapObject}}) + bullet = Bodies.circle(300, 300, 25, JsObject{isStatic: false, frictionAir: 0, friction: 1, plugin: JsObject{wrap: wrapObject()}}) Body.setInertia(bullet, Infinity) # Body.setAngle(bullet, degToRad(180d)) # constraint = Constraint.create(JsObject{pointA: jsVector(400, 300), bodyB: bullet, length: 30, stiffness: 0.1}) - floor = Bodies.rectangle(350, 495, 1200, floorHeight, JsObject{isStatic: true}) + ground = Bodies.rectangle(canvas.clientWidth / 2, + canvas.clientHeight + (groundHeight div 2), canvas.clientWidth * 1000, + groundHeight, JsObject{isStatic: true} + ) # 350, 495, 1200 + + thingy = Bodies.rectangle(500, 350, 20, 80, JsObject{isStatic: false, plugin: JsObject{wrap: wrapObject()}}) mouse = Mouse.create(canvas) mconstraint = MouseConstraint.create(engine, JsObject{mouse: mouse}) @@ -89,10 +109,9 @@ proc load*() = # Walls Bodies.rectangle(350, -200, 1000, 20, JsObject{isStatic: true}), # up # Bodies.rectangle(690, 250, 20, 500, JsObject{isStatic: true}), # right - floor, # down + ground, # down # Bodies.rectangle(10, 250, 20, 500, JsObject{isStatic: true}), # left - # Thingy - Bodies.rectangle(500, 350, 20, 80, JsObject{isStatic: false, plugin: JsObject{wrap: wrapObject}}) + thingy, ]) @@ -118,7 +137,7 @@ proc load*() = if exerciseStatus == esStarted: let pos = bullet.position - drawArrow(mrender.context, pos.x, pos.y, pos.x + (bullet.velocity.x * toJs 10), pos.y + (bullet.velocity.y * toJs 10), toJs 5, toJs cstring"white") + drawArrow(mrender.context, pos.x, pos.y, pos.x + (bullet.velocity.x * toJs 5), pos.y + (bullet.velocity.y * toJs 5), toJs 5, toJs cstring"white") mrender.context.globalAlpha = 1 Render.endViewTransform(mrender) @@ -144,9 +163,9 @@ proc normalizeAngle(rad: float): int = result = 360 - result # echo "final ", result -## Since matter measures y from the top of the screen, here we "normalize" it so that the 0 starts at the floor +## Since matter measures y from the top of the screen, here we "normalize" it so that the 0 starts at the ground proc normalizeY(y: int): int = - -y + (floor.position.y.to(int) - (floorHeight div 2) - bullet.circleRadius.to(int)) + -y + (ground.position.y.to(int) - (groundHeight div 2) - bullet.circleRadius.to(int)) proc sendBulletFlying(changeStatus = true) = if paused: return @@ -237,8 +256,8 @@ proc calcTrajectory() {.async.} = # print bullet.jsonStringify()#Composite.allBodies(engine.world).jsonStringify() proc renderTextDiv*(): VNode = - buildHtml tdiv(style = "float: left; width: 40%;".toCss): - p(text r"\(t_f = \frac{2 \cdot v_i \cdot \sin(\theta)}{g}\)", style = "font-size: 80px;".toCss) + buildHtml tdiv(style = "".toCss): + p(text r"\(t_f = \frac{2 \cdot v_i \cdot \sin(\theta)}{g}\)", style = "font-size: 50px;".toCss) if not bullet.isNil: p(text &"x = {int bullet.position.x.to(float)} y = {normalizeY(int bullet.position.y.to(float))}") @@ -260,7 +279,7 @@ proc renderSimDiv*(): VNode = discard calcTrajectory() exerciseStatus = esStart - buildHtml tdiv(style = "float: right; width: 40%;".toCss): + buildHtml tdiv(style = "".toCss): button(): #text "Pause/Resume" if engine.isNil: @@ -292,53 +311,7 @@ proc renderSimDiv*(): VNode = discard calcTrajectory() button(): - verbatim """ - - - - - - - - """ + verbatim parabolaIconSvg #img(src = "/public/img/parabola.svg", alt = "Parabola Trajectory") proc onclick() = discard calcTrajectory() #text "Trajectory" @@ -349,22 +322,36 @@ proc renderSimDiv*(): VNode = proc onclick() = sendBulletFlying() - canvas(id = "canvas", style = fmt"width: {canvasWidth}px; height: {canvasHeight}px; background: rgb(20, 21, 31)".toCss): + br() + + #canvas(id = "canvas", style = fmt"width: {canvasWidth}px; height: {canvasHeight}px; background: rgb(20, 21, 31)".toCss): + canvas(id = "canvas", style = fmt"width: 50vw; min-width: 300px; height: 50vh; min-height: 100px; background: rgb(20, 21, 31)".toCss): text "Matter-js simulation" - tdiv(id = "exercises", style = "height: 200px; overflow-y: auto;".toCss): + tdiv(id = "exercises", style = "min-height: 0px; overflow-y: auto;".toCss): for e, exercise in exercises: if e == 0: continue # First exercise is the default exercise - button(onclick = exerciseOnClick(e)): - text &"#{e} angle = {exercise.angle} vi = {exercise.speed} pos = ({exercise.pos.x}, {exercise.pos.x})" + tdiv(style = "display: block;".toCss): + button(onclick = exerciseOnClick(e)): + text &"#{e} angle = {exercise.angle} vi = {exercise.speed} pos = ({exercise.pos.x}, {exercise.pos.x})" proc render*(): VNode = - buildHtml tdiv(style = "width: 100%; justify-content: center; align-items: center;".toCss): + buildHtml tdiv(style = "display: flex; flex-flow: column wrap; justify-content: start; align-items: center;".toCss): renderTextDiv() renderSimDiv() -document.addEventListener("keyup", proc (event: Event) = +window.addEventListener("resize", proc(event: Event) = + mrender.canvas.width = canvas.clientWidth + mrender.canvas.height = canvas.clientHeight + + bullet.plugin.wrap = wrapObject() + thingy.plugin.wrap = wrapObject() + + Body.setPosition(ground, JsObject{x: canvas.clientWidth / 2, y: canvas.clientHeight + (groundHeight div 2)}) +) + +document.addEventListener("keyup", proc(event: Event) = let event = KeyboardEvent(event) case $event.key of "t": diff --git a/src/frontend/simulations/utils.nim b/src/frontend/simulations/utils.nim index e31969e..648b6a7 100644 --- a/src/frontend/simulations/utils.nim +++ b/src/frontend/simulations/utils.nim @@ -2,6 +2,54 @@ import std/[jsffi, math] import matter +const + parabolaIconSvg* = """ + + + + + + + +""" + var Infinity* {.importjs, nodecl.}: JsObject MathJax* {.importjs, nodecl.}: JsObject