From 7f68ab23ca364e5059346fb69270d96d967c058a Mon Sep 17 00:00:00 2001 From: Cristobal Date: Mon, 27 May 2024 21:02:34 -0500 Subject: [PATCH] Worked on the canon --- src/frontend/simulations/parabola.nim | 157 +++++++++++++++----------- 1 file changed, 92 insertions(+), 65 deletions(-) diff --git a/src/frontend/simulations/parabola.nim b/src/frontend/simulations/parabola.nim index 0f32394..d70c01b 100644 --- a/src/frontend/simulations/parabola.nim +++ b/src/frontend/simulations/parabola.nim @@ -1,4 +1,4 @@ -import std/[math, jsffi, dom, jsconsole, enumerate, with, strformat, asyncjs] +import std/[math, jsffi, dom, jsconsole, enumerate, with, strformat, asyncjs, algorithm] import karax/[karax, karaxdsl, vdom, vstyles] import matter, utils @@ -51,40 +51,70 @@ proc initCanonState(angleRad: float, speed: int): CanonState = let angleDeg = int radToDeg(float angleRad) CanonState(angleDeg: angleDeg, angleRad: angleRad, speed: speed, velocity: speedToVelRad(float speed, angleRad)) -proc initParabolaState*(): ParabolaState = - ParabolaState(canon: Canon(bulletRadius: 20, state: initCanonState(45,25))) - const deltaTime = 1000 / 60 # 60fps, 60 times in one second (1000 milliseconds) secPerFrame = 1 / 60 - timeScale = 0.5 + timeScale = 0.4 canvasWidth = 700 canvasHeight = 500 groundHeight = 20 - canonWidth = 100 - canonHeight = 50 - canonX = 100 + canonWidth = 120 + canonHeight = 70 + canonX = canonWidth canonY = canvasHeight - groundHeight - canonHeight + canonRotationDeg = 20d + canonInitialSpeed = 28 canonTexture = "/public/img/canon.png" trajectoryColor = "orange" + velocityVectorScale = 8 + let canonPivot = JsObject{x: canonX - (canonWidth / 2), y: canonY} -proc wrapObject(state: ParabolaState): JsObject = - JsObject{min: JsObject{x: 0, y: undefined}, max: JsObject{x: state.canvas.clientWidth, y: undefined}} # To avoid boilerplate +proc initParabolaState*(): ParabolaState = + ParabolaState(canon: Canon( + bulletRadius: 20, state: initCanonState(0, canonInitialSpeed), + bulletOptions: JsObject{ + isStatic: false, frictionAir: 0, friction: 1, + }, + )) proc `bullet`(canon: Canon): JsObject = assert canon.currentBullet in canon.bullets.low..canon.bullets.high, &"Invalid bullet index {canon.currentBullet}" canon.bullets[canon.currentBullet] +proc wrapObject(state: ParabolaState): JsObject = + JsObject{min: JsObject{x: 0, y: undefined}, max: JsObject{x: state.canvas.clientWidth, y: undefined}} # To avoid boilerplate + #proc `bullet`(state: var CanonState): var JsObject = # state.bullets[state.currentBullet] +## Since a body's angle can be negative and can be higher than 360, this procedure makes it so it's always positive and inside 0..359 +proc normalizeAngle(rad: float): int = + result = int rad.radToDeg() + result -= (result div 360) * 360 # Remove excess rotations + # echo result + if result < 0: + result = abs result + elif result > 0: + result = 360 - result + # echo "final ", result + +proc rotate(canon: var Canon, rad = degToRad(canonRotationDeg)) = + Body.rotate(canon.body, rad, canonPivot) + + canon.state.angleDeg = normalizeAngle(canon.body.angle.to(float)) + canon.state.angleRad = degToRad(float canon.state.angleDeg) + canon.state.velocity = speedToVelRad(float canon.state.speed, canon.state.angleRad) + +proc rotateBack(canon: var Canon, rad = degToRad(canonRotationDeg)) = + canon.rotate(-rad) + ## Loads the simulation proc load*(state: var ParabolaState) = # Render all MathJax expressions asynchronously @@ -114,21 +144,24 @@ proc load*(state: var ParabolaState) = Runner.run(state.runner, state.engine) # Create and add all bodies to the world + state.canon.bulletOptions.plugin = JsObject{wrap: state.wrapObject()} state.canon.body = Bodies.rectangle(canonX, canonY, canonWidth, canonHeight, JsObject{ - isStatic: true, collisionFilter: JsObject{mask: 0}, render: JsObject{sprite: JsObject{ + isStatic: true, collisionFilter: JsObject{category: 0x2, mask: 0}, label: cstring"Canon", + render: JsObject{sprite: JsObject{ texture: cstring canonTexture, xOffset: 0, yOffset: 0 }} }) + state.canon.rotateBack(degToRad(60d)) #constraint = Constraint.create(JsObject{pointA: jsVector(0, 0), bodyB: canon})#, length: 30, stiffness: 0.1}) state.ground = Bodies.rectangle(state.canvas.clientWidth / 2, state.canvas.clientHeight + (groundHeight div 2), state.canvas.clientWidth * 1000, - groundHeight, JsObject{isStatic: true} + groundHeight, JsObject{isStatic: true, label: cstring"Groubd"} ) # 350, 495, 1200 - state.thingy = Bodies.rectangle(500, 350, 20, 80, JsObject{isStatic: false, plugin: JsObject{wrap: state.wrapObject}}) + state.thingy = Bodies.rectangle(500, 350, 20, 80, JsObject{isStatic: false, label: cstring"Thingy", plugin: JsObject{wrap: state.wrapObject}}) state.mouse = Mouse.create(state.canvas) state.mouseConstraint = MouseConstraint.create(state.engine, JsObject{mouse: state.mouse, collisionFilter: JsObject{mask: 0}}) @@ -143,11 +176,10 @@ proc load*(state: var ParabolaState) = ]) Events.on(state.mouseConstraint, "mousedown", proc(event: JsObject) = - if Bounds.contains(state.canon.body.bounds, event.mouse.position).to(bool):# and event.mouse.button == toJs 0: + if Bounds.contains(state.canon.body.bounds, event.mouse.position).to(bool): state.canon.isDragging = true ) - Events.on(state.mouseConstraint, "mouseup", proc(event: JsObject) = state.canon.isDragging = false ) @@ -158,13 +190,15 @@ proc load*(state: var ParabolaState) = #if not kxi.surpressRedraws: redraw(kxi) # TODO: REALLY INEFFICIENT if state.canon.isDragging: let targetAngle = Vector.angle(canonPivot, state.mouse.position) - Body.rotate(state.canon.body, targetAngle - state.canon.body.angle, canonPivot) + state.canon.rotate(to(targetAngle - state.canon.body.angle, float)) ) Events.on(state.engine, "collisionStart", proc(event: JsObject) = - if state.canon.bullets.len > 0 and state.canon.status == csFlight and - event.pairs[0].bodyA.id == state.canon.bullet.id: - state.canon.status = csHit + if state.canon.bullets.len > 0 and state.canon.status == csFlight: + for pair in items(event.pairs): + if pair.bodyA.id == state.canon.bullet.id or pair.bodyB.id == state.canon.bullet.id: + state.canon.status = csHit + break ) Events.on(state.render, "afterRender", proc() = @@ -177,16 +211,35 @@ proc load*(state: var ParabolaState) = if state.canon.bullets.len > 0 and state.canon.status == csFlight: let pos = state.canon.bullet.position + print state.canon.bullet.velocity + drawArrow(state.render.context, pos.x, pos.y, + pos.x, + pos.y + (state.canon.bullet.velocity.y * toJs velocityVectorScale), + toJs 4, toJs cstring"red" + ) + drawArrow(state.render.context, pos.x, pos.y, - pos.x + (state.canon.bullet.velocity.x * toJs 7), - pos.y + (state.canon.bullet.velocity.y * toJs 7), - toJs 4, toJs cstring"white" + pos.x + (state.canon.bullet.velocity.x * toJs velocityVectorScale), + pos.y, + toJs 4, toJs cstring"blue" ) + #drawArrow(state.render.context, pos.x, pos.y, + # pos.x + (state.canon.bullet.velocity.x * toJs 9), + # pos.y + (state.canon.bullet.velocity.y * toJs 9), + # toJs 4, toJs cstring"white" + #) + state.render.context.globalAlpha = 1 Render.endViewTransform(state.render) ) + Events.on(state.engine.world, "afterAdd", proc() = + state.engine.world.bodies = state.engine.world.bodies.to(seq[JsObject]).sorted(proc(a, b: JsObject): int = + to(a.collisionFilter.category - b.collisionFilter.category, int) + ) + ) + ## Reloads the simulation proc reload*(state: var ParabolaState) = Composite.clear(state.engine.world) @@ -196,36 +249,19 @@ proc reload*(state: var ParabolaState) = state.canon.trajectory.setLen(0) state.load() -## Since a body's angle can be negative and can be higher than 360, this procedure makes it so it's always positive and inside 0..359 -proc normalizeAngle(rad: float): int = - result = int rad.radToDeg() - result -= (result div 360) * 360 # Remove excess rotations - # echo result - if result < 0: - result = abs result - elif result > 0: - 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 ground proc normalizeBulletY(state: ParabolaState, y: int, bulletRadius: int): int = -y + (state.ground.position.y.to(int) - (groundHeight div 2) - bulletRadius) -proc rotate(canon: var Canon, clockwise = true) = - var rad = degToRad(20d)#(360 / bullet.vertices.length.to(float64))*2) - if not clockwise: - rad = -rad - - Body.rotate(canon.body, rad, canonPivot) - - canon.state.angleDeg = normalizeAngle(canon.body.angle.to(float)) - canon.state.angleRad = degToRad(float canon.state.angleDeg) - canon.state.velocity = speedToVelRad(float canon.state.speed, canon.state.angleRad) - proc fireBullet(state: var ParabolaState) = - let min = state.canon.body.bounds.min - let max = state.canon.body.bounds.max - let bullet = Bodies.circle(max.x, min.y, state.canon.bulletRadius, state.canon.bulletOptions) + let vertice1 = state.canon.body.vertices[1] + let vertice2 = state.canon.body.vertices[2] + + let bullet = Bodies.circle( + (vertice1.x + vertice2.x) / toJs 2, + (vertice1.y + vertice2.y) / toJs 2, + state.canon.bulletRadius, state.canon.bulletOptions + ) #Body.setInertia(bullet, Infinity) #echo (stateAngle: state.canon.state.angleDeg, state) @@ -257,8 +293,10 @@ proc calcTrajectory(state: var ParabolaState) {.async.} = # Body.setStatic(body, true) proc onCollision(event: JsObject) = - if event.pairs[0].bodyA.id == bullet.id: - stop = true + for pair in items(event.pairs): + if pair.bodyA.id == bullet.id or pair.bodyB.id == bullet.id: + stop = true + break Events.on(state.engine, "collisionStart", onCollision) @@ -333,13 +371,13 @@ proc renderSimDiv*(state: var ParabolaState): VNode = button(): span(class = "material-symbols-outlined", text "rotate_left") proc onclick() = - state.canon.rotate(clockwise = false) + state.canon.rotateBack() discard state.calcTrajectory() button(): span(class = "material-symbols-outlined", text "rotate_right") proc onclick() = - state.canon.rotate(clockwise = true) + state.canon.rotate() discard state.calcTrajectory() #button(): @@ -359,17 +397,6 @@ proc renderSimDiv*(state: var ParabolaState): VNode = canvas(id = "canvas", style = fmt"width: 50vw; min-width: 300px; height: 50vh; min-height: 300px; background: rgb(20, 21, 31)".toCss): text "Matter-js simulation" -#proc exerciseOnClick(current: int): proc = -# proc () = -# let exercise = exercises[current] -# let bullet = bullets[currentBullet] - -# curExercise = current -# Body.setPosition(bullet, jsVector(exercise.pos.x, state.normalizeBulletY(exercise.pos.y))) -# Body.setAngle(bullet, degToRad(float(360 - exercise.angle))) -# discard calcTrajectory() -# exerciseStatus = csReady - proc render*(state: var ParabolaState): VNode = buildHtml tdiv(style = "display: flex; flex-direction: column; justify-content: start; align-items: center; height: 100%;".toCss): state.renderTextDiv() @@ -405,12 +432,12 @@ proc addEventListeners*(state: var ParabolaState) = of "t": discard state.calcTrajectory() of "ArrowRight": - state.canon.rotate(clockwise = true) + state.canon.rotate() discard state.calcTrajectory() of "ArrowLeft": - state.canon.rotate(clockwise = false) + state.canon.rotateBack() discard state.calcTrajectory() - of "ArrowUp": + of "ArrowUp", " ": state.fireBullet() of "Backspace": state.reload()