Skip to content

Commit

Permalink
Worked on the canon
Browse files Browse the repository at this point in the history
  • Loading branch information
Patitotective committed May 28, 2024
1 parent d5167ab commit 7f68ab2
Showing 1 changed file with 92 additions and 65 deletions.
157 changes: 92 additions & 65 deletions src/frontend/simulations/parabola.nim
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}})
Expand All @@ -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
)
Expand All @@ -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() =
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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():
Expand All @@ -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()
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 7f68ab2

Please sign in to comment.