{"id":2518,"date":"2022-04-19T19:55:53","date_gmt":"2022-04-19T23:55:53","guid":{"rendered":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2518"},"modified":"2022-04-19T19:56:01","modified_gmt":"2022-04-19T23:56:01","slug":"visual-crit-jud","status":"publish","type":"post","link":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2518","title":{"rendered":"Visual Crit &#8211; Jud"},"content":{"rendered":"<h2>Problem:<\/h2>\n<p>Many rooms in the basements of buildings are very depressing and hard to work in because of the lack of windows and access to nature. Looking out the window is a very comforting and calming action that one can take for granted when working on floors where there is plenty of access to windows. Not only being able to look out the window and see what the weather is currently, but also being able to see nature outside is very helpful to productivity. In order to solve this problem, this project attempts to make a virtual window that allows you to see the outside environment and also interact with it. The p5.js script reads the weather of the desired location and inputs that into the script which then decides what to do based on what the weather is. For example, if it&#8217;s raining outside, the visual feedback will be a rain simulation with the rain droplets accumulating at the bottom to show how much it has actually rained. The same thing happens with snow and other weather events that have a physical element falling to the ground. In addition to the simulation of the weather, the device will have a gyroscope inside reading the current tilt angle and sending that back into the p5 script. With this tilt information, the accumulated physical weather element will then move around the screen to be level with the ground.<\/p>\n<p>The following images and videos show part of the system working with light rain, heavy rain, and snow. The current script is able to read the weather and report back to the user what it is outside. The simulation of the physical weather elements are also able to move around corresponding to the tilt angle accumulating at the bottom until the weather type changes.<\/p>\n<h2>Images:<\/h2>\n<figure id=\"attachment_2524\" aria-describedby=\"caption-attachment-2524\" style=\"width: 2560px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" class=\"size-full wp-image-2524\" src=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-scaled.jpg\" alt=\"\" width=\"2560\" height=\"1920\" srcset=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-scaled.jpg 2560w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-300x225.jpg 300w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-1024x768.jpg 1024w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-768x576.jpg 768w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-1536x1152.jpg 1536w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-2048x1536.jpg 2048w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0853-1200x900.jpg 1200w\" sizes=\"(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-2524\" class=\"wp-caption-text\">Image of snow weather<\/figcaption><\/figure>\n<figure id=\"attachment_2525\" aria-describedby=\"caption-attachment-2525\" style=\"width: 1920px\" class=\"wp-caption alignnone\"><img loading=\"lazy\" class=\"size-full wp-image-2525\" src=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-scaled.jpg\" alt=\"\" width=\"1920\" height=\"2560\" srcset=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-scaled.jpg 1920w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-225x300.jpg 225w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-768x1024.jpg 768w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-1152x1536.jpg 1152w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-1536x2048.jpg 1536w, https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0847-1200x1600.jpg 1200w\" sizes=\"(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption id=\"caption-attachment-2525\" class=\"wp-caption-text\">Physical build of gyroscope sensor and teensy<\/figcaption><\/figure>\n<h2>Videos:<\/h2>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1ZXIuslsgqisttRYhJBdXOgitN-gbGOwy\/view?usp=sharing\">Video of Snow<\/a><\/p>\n<p><a href=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/wp-content\/uploads\/2022\/04\/IMG_0849.mov\">Video of Light Rain<\/a><\/p>\n<p><a href=\"https:\/\/drive.google.com\/file\/d\/1B-1uJGoBYQJpu8D88UbMaUqk6Fu-6pgM\/view?usp=sharing\">Video of Heavy Rain<\/a><\/p>\n<h2>Code:<\/h2>\n<h4>Arduino<\/h4>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\/*\r\n    MPU6050 Triple Axis Gyroscope &amp; Accelerometer. Simple Gyroscope Example.\r\n    Read more: http:\/\/www.jarzebski.pl\/arduino\/czujniki-i-sensory\/3-osiowy-zyroskop-i-akcelerometr-mpu6050.html\r\n    GIT: https:\/\/github.com\/jarzebski\/Arduino-MPU6050\r\n    Web: http:\/\/www.jarzebski.pl\r\n    (c) 2014 by Korneliusz Jarzebski\r\n*\/\r\n\r\n#include &lt;Wire.h&gt;\r\n#include &lt;MPU6050.h&gt;\r\n\r\nMPU6050 mpu;\r\n\r\ndouble zPrev = 0;\r\n\r\nvoid setup()\r\n{\r\n  Serial.begin(115200);\r\n\r\n  \/\/ Initialize MPU6050\r\n  Serial.println(\"Initialize MPU6050\");\r\n  while (!mpu.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G))\r\n  {\r\n    Serial.println(\"Could not find a valid MPU6050 sensor, check wiring!\");\r\n    delay(500);\r\n  }\r\n\r\n  \/\/ If you want, you can set gyroscope offsets\r\n     mpu.setAccelOffsetX(40);\r\n     mpu.setAccelOffsetY(40);\r\n     mpu.setAccelOffsetZ(40);\r\n\r\n  \/\/ Calibrate gyroscope. The calibration must be at rest.\r\n  \/\/ If you don't want calibrate, comment this line.\r\n  mpu.calibrateGyro();\r\n\r\n  \/\/ Set threshold sensivty. Default 3.\r\n  \/\/ If you don't want use threshold, comment this line or set 0.\r\n  mpu.setThreshold(3);\r\n\r\n  \/\/ Check settings\r\n  checkSettings();\r\n}\r\n\r\nvoid checkSettings()\r\n{\r\n\/\/  Serial.println();\r\n\r\n  Serial.print(\" * Sleep Mode:        \");\r\n  Serial.println(mpu.getSleepEnabled() ? \"Enabled\" : \"Disabled\");\r\n\r\n  Serial.print(\" * Clock Source:      \");\r\n  switch (mpu.getClockSource())\r\n  {\r\n    case MPU6050_CLOCK_KEEP_RESET:     Serial.println(\"Stops the clock and keeps the timing generator in reset\"); break;\r\n    case MPU6050_CLOCK_EXTERNAL_19MHZ: Serial.println(\"PLL with external 19.2MHz reference\"); break;\r\n    case MPU6050_CLOCK_EXTERNAL_32KHZ: Serial.println(\"PLL with external 32.768kHz reference\"); break;\r\n    case MPU6050_CLOCK_PLL_ZGYRO:      Serial.println(\"PLL with Z axis gyroscope reference\"); break;\r\n    case MPU6050_CLOCK_PLL_YGYRO:      Serial.println(\"PLL with Y axis gyroscope reference\"); break;\r\n    case MPU6050_CLOCK_PLL_XGYRO:      Serial.println(\"PLL with X axis gyroscope reference\"); break;\r\n    case MPU6050_CLOCK_INTERNAL_8MHZ:  Serial.println(\"Internal 8MHz oscillator\"); break;\r\n  }\r\n\r\n\/\/  Serial.print(\" * Gyroscope:         \");\r\n  switch (mpu.getScale())\r\n  {\r\n    case MPU6050_SCALE_2000DPS:        Serial.println(\"2000 dps\"); break;\r\n    case MPU6050_SCALE_1000DPS:        Serial.println(\"1000 dps\"); break;\r\n    case MPU6050_SCALE_500DPS:         Serial.println(\"500 dps\"); break;\r\n    case MPU6050_SCALE_250DPS:         Serial.println(\"250 dps\"); break;\r\n  }\r\n\r\n  Serial.print(\" * Gyroscope offsets: \");\r\n  Serial.print(mpu.getGyroOffsetX());\r\n  Serial.print(\" \/ \");\r\n  Serial.print(mpu.getGyroOffsetY());\r\n  Serial.print(\" \/ \");\r\n  Serial.println(mpu.getGyroOffsetZ());\r\n\r\n  Serial.println();\r\n}\r\n\r\nvoid loop()\r\n{\r\n  Vector rawGyro = mpu.readRawAccel();\r\n  Vector normGyro = mpu.readNormalizeAccel();\r\n\r\n  \/\/  Serial.print(\" Xraw = \");\r\n  \/\/  Serial.print(rawGyro.XAxis);\r\n  \/\/  Serial.print(\" Yraw = \");\r\n  \/\/  Serial.print(rawGyro.YAxis);\r\n  \/\/  Serial.print(\" Zraw = \");\r\n  \/\/  Serial.println(rawGyro.ZAxis);\r\n\r\n  \/\/  Serial.print(\" Xnorm = \");\r\n  double x = rawGyro.XAxis;\r\n  double z = rawGyro.ZAxis;\r\n  \r\n  if (z &lt; 20000) {\r\n    z = z + 65000;\r\n  }\r\n  z = z*0.8 + 0.2*zPrev;\r\n\/\/  Serial.print(78000 - z);\r\n\/\/  Serial.print(\"\\t\");\r\n  \r\n  z = (78000 - z)\/(78000 - 30000)*180;\r\n  z = z - 130;\r\n  Serial.print(z);\r\n\/\/  Serial.print(\"\\t\");\r\n\r\n  zPrev = z;\r\n\r\n  Serial.println();\r\n\r\n  delay(10);\r\n}<\/pre>\n<h4>P5.JS<\/h4>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">var minWidth = 600;   \/\/set min width and height for canvas\r\nvar minHeight = 400;\r\nvar width, height;    \/\/ actual width and height for the sketch\r\nvar squareWidth = 100;\r\n\r\nlet serial;\r\nlet latestData = \"waiting for data\";\r\nvar outData;\r\n\r\n\/\/ A wind direction vector\r\nlet wind;\r\n\/\/ Circle position\r\nlet position;\r\nvar weather = 'sn';\r\nvar weather_String = 'Snow';\r\nvar startHeight = 50;\r\nvar currColor;\r\n\r\nvar currWidth = 0;\r\nvar currHeight = 0;\r\n\r\nlet angle = 0;\r\n\r\nlet windAngle = 0.0;\r\nlet windMag = 0.0;\r\n\r\nvar startTime = 0;\r\nvar waitTime = 1000;\r\nvar rainAccumulated = 0;\r\nvar numDrops = 0;\r\n\r\nlet numBalls = 0;\r\nlet spring = 0.01;\r\nlet gravity = 0.03;\r\nlet friction = -0.2;\r\nlet balls = [];\r\n\r\nfunction setup() {\r\n  if (window.innerWidth &gt; minWidth){\r\n  width = window.innerWidth;\r\n  } else {\r\n    width = minWidth;\r\n  }\r\n  if (window.innerHeight &gt; minHeight) {\r\n    height = window.innerHeight;\r\n  } else {\r\n    height = minHeight;\r\n  }\r\n  \/\/set up canvas\r\n  createCanvas(width, height);\r\n  noStroke();\r\n  \/\/ Request the data from metaweather.com\r\n  \/\/ let url = 'https:\/\/cors-anywhere.herokuapp.com\/https:\/\/www.metaweather.com\/api\/location\/2357536\/';\r\n  \/\/ loadJSON(url,gotWeather);\r\n  \/\/ \/\/ Circle starts in the middle\r\n  \/\/ position = createVector(width\/2, height\/2);\r\n  \/\/ \/\/ wind starts as (0,0)\r\n  \/\/ wind = createVector();\r\n  \/\/ noStroke();\r\n  \/\/ fill(255, 204);\r\n\r\n  serial = new p5.SerialPort();\r\n\r\n  serial.list();\r\n  serial.open('\/dev\/tty.usbmodem114760901');\r\n\r\n  serial.on('connected', serverConnected);\r\n\r\n  serial.on('list', gotList);\r\n\r\n  serial.on('data', gotData);\r\n\r\n  serial.on('error', gotError);\r\n\r\n  serial.on('open', gotOpen);\r\n\r\n  serial.on('close', gotClose);\r\n\r\n  angleMode(DEGREES);\r\n\r\n}\r\n\r\nfunction serverConnected() {\r\n  print(\"Connected to Server\");\r\n }\r\n \r\n function gotList(thelist) {\r\n  print(\"List of Serial Ports:\");\r\n \r\n  for (let i = 0; i &lt; thelist.length; i++) {\r\n   print(i + \" \" + thelist[i]);\r\n  }\r\n }\r\n \r\n function gotOpen() {\r\n  print(\"Serial Port is Open\");\r\n }\r\n \r\n function gotClose(){\r\n  print(\"Serial Port is Closed\");\r\n  latestData = \"Serial Port is Closed\";\r\n }\r\n \r\n function gotError(theerror) {\r\n  print(theerror);\r\n }\r\n \r\n function gotData() {\r\n   let currentString = serial.readLine();\r\n   trim(currentString);\r\n   if (!currentString) return;\r\n   console.log(currentString);\r\n   latestData = Number(currentString);\r\n   angle = latestData;\r\n }\r\n\r\nfunction draw() {\r\n  background(220);\r\n  \r\n  \/\/Draw Text\r\n  \/\/ translate(width\/2, height\/2);\r\n  \/\/ rotate(angle);\r\n  fill('black')\r\n  textAlign(CENTER);\r\n  textSize(50);\r\n  text(weather_String, width\/2, 60);\r\n  textSize(20);\r\n  var windText = 'Wind Speed: ' + windMag + '\\t Wind Angle: ' + windAngle;\r\n  text(windText, width\/2, 100);\r\n  text(latestData, width\/2, 130);\r\n  \r\n  updateWeather();\r\n  \r\n  \/\/ angle = cos(millis()\/1000);\r\n  \r\n  if (millis() - startTime &gt; waitTime) {\r\n    balls[numBalls] = new Ball(random(width), 0, random(20, 30), random(30, 50), numBalls, balls);\r\n    numBalls +=1;\r\n    startTime = millis();\r\n  }\r\n  \r\n  balls.forEach(ball =&gt; {\r\n    ball.collide();\r\n    ball.move();\r\n    ball.display();\r\n  });\r\n  \r\n  \r\n  \/\/ \/\/ This section draws an arrow pointing in the direction of wind\r\n  \/\/ push();\r\n  \/\/ translate(32, height - 32);\r\n  \/\/ \/\/ Rotate by the wind's angle\r\n  \/\/ rotate(wind.heading() + PI\/2);\r\n  \/\/ noStroke();\r\n  \/\/ fill(255);\r\n  \/\/ ellipse(0, 0, 48, 48);\r\n\r\n  \/\/ stroke(45, 123, 182);\r\n  \/\/ strokeWeight(3);\r\n  \/\/ line(0, -16, 0, 16);\r\n\r\n  \/\/ noStroke();\r\n  \/\/ fill(45, 123, 182);\r\n  \/\/ triangle(0, -18, -6, -10, 6, -10);\r\n  \/\/ pop();\r\n  \r\n  \/\/ \/\/ Move in the wind's direction\r\n  \/\/ position.add(wind);\r\n  \r\n  \/\/ stroke(0);\r\n  \/\/ fill(51);\r\n  \/\/ ellipse(position.x, position.y, 16, 16);\r\n\r\n  \/\/ if (position.x &gt; width)  position.x = 0;\r\n  \/\/ if (position.x &lt; 0)      position.x = width;\r\n  \/\/ if (position.y &gt; height) position.y = 0;\r\n  \/\/ if (position.y &lt; 0)      position.y = height;\r\n}\r\n\r\nfunction gotWeather(weather) {\r\n  print(weather.title);\r\n  let weather_today = weather.consolidated_weather[0]\r\n  weather = weather_today.weather_state_abbr;\r\n  weather_String = weather_today.weather_state_name;\r\n  \/\/ print(weather);\r\n  \/\/ print(weather_String);\r\n  \/\/ Get the angle (convert to radians)\r\n  windAngle = radians(Number(weather_today.wind_direction));\r\n  \/\/ Get the wind speed\r\n  windMag = Number(weather_today.wind_speed)\/100;\r\n  \r\n  \/\/ Display as HTML elements\r\n  \/\/ let temperatureDiv = createDiv(floor(weather_today.the_temp) + '&amp;deg;C');\r\n  \/\/ let windDiv = createDiv(\"WIND \" + windmag + \" &lt;small&gt;MPH&lt;\/small&gt;\");\r\n  \r\n  \/\/ Make a vector\r\n  wind = p5.Vector.fromAngle(angle);\r\n}\r\n\r\nfunction updateWeather() {\r\n  switch(weather){\r\n    case 'sn':\r\n      waitTime = 300;\r\n      fill('white')\r\n      currWidth = 30;\r\n      currHeight = 30;\r\n      weather_String = 'Snow';\r\n      break;\r\n\r\n    case 'sl':\r\n      weather_String = 'Sleet';\r\n      break;\r\n    case 'h':\r\n      weather_String = 'Hail';\r\n      break;\r\n    case 't':\r\n      weather_String = 'Thunderstorms';\r\n      break;\r\n\r\n    case 'hr':\r\n      weather_String = 'Heavy Rain';\r\n      waitTime = 100;\r\n      fill(138, 183, 255)\r\n      currWidth = 30;\r\n      currHeight = 30;\r\n      accumulate = true;\r\n      break;\r\n\r\n    case 'lr':\r\n      weather_String = 'Light Rain';\r\n      waitTime = 2000;\r\n      fill(138, 183, 255)\r\n      currWidth = 30;\r\n      currHeight = 30;\r\n      accumulate = true;\r\n      break;\r\n\r\n    case 's':\r\n      weather_String = 'Showers';\r\n      break;\r\n\r\n    case 'hc':\r\n      weather_String = 'Heavy Clouds';\r\n\r\n      break;\r\n\r\n    case 'lc':\r\n      weather_String = 'Light Clouds';\r\n\r\n      break;\r\n\r\n    case 'c':\r\n      weather_String = 'Clear';\r\n\r\n      break;\r\n\r\n    default:\r\n      break;\r\n  }\r\n}\r\n\r\nclass Ball {\r\n  constructor(xin, yin, win, hin, idin, oin) {\r\n    this.x = xin;\r\n    this.y = yin;\r\n    this.vx = 0;\r\n    this.vy = 0;\r\n    this.w = win;\r\n    this.h = hin\r\n    this.id = idin;\r\n    this.others = oin;\r\n    \/\/ this.vxAdd = windMag*cos(windAngle);\r\n    \/\/ this.vyAdd = windMag*sin(windAngle);\r\n    this.vxAdd = 0;\r\n    this.vyAdd = 0;\r\n  }\r\n\r\n  collide() {\r\n    for (let i = this.id + 1; i &lt; numBalls; i++) {\r\n      \/\/ console.log(others[i]);\r\n      let dx = this.others[i].x - this.x;\r\n      let dy = this.others[i].y - this.y;\r\n      let distance = sqrt(dx * dx + dy * dy);\r\n      let minDist = this.others[i].w \/ 2 + this.w \/ 2;\r\n      \/\/   console.log(distance);\r\n      \/\/console.log(minDist);\r\n      if (distance &lt; minDist) {\r\n        \/\/console.log(\"2\");\r\n        let angle = atan2(dy, dx);\r\n        let targetX = this.x + cos(angle) * minDist;\r\n        let targetY = this.y + sin(angle) * minDist;\r\n        let ax = (targetX - this.others[i].x) * spring;\r\n        let ay = (targetY - this.others[i].y) * spring;\r\n        this.vx -= ax;\r\n        this.vy -= ay;\r\n        this.others[i].vx += ax;\r\n        this.others[i].vy += ay;\r\n      }\r\n    }\r\n  }\r\n\r\n  move() {\r\n    var ygravity = gravity*cos(angle);\r\n    var xgravity = gravity*sin(angle);\r\n    this.vy += ygravity + this.vyAdd;\r\n    this.vx += xgravity + this.vxAdd;\r\n    this.x += this.vx;\r\n    this.y += this.vy;\r\n    if (this.x + this.w \/ 2 &gt; width) {\r\n      this.vxAdd = 0;\r\n      \/\/ print(\"Hello\");\r\n      this.x = width - this.w \/ 2;\r\n      this.vx *= friction;\r\n    } else if (this.x - this.w \/ 2 &lt; 0) {\r\n      this.vxAdd = 0;\r\n      this.x = this.w \/ 2;\r\n      this.vx *= friction;\r\n    }\r\n    if (this.y + this.w \/ 2 &gt; height) {\r\n      this.vyAdd = 0;\r\n      this.y = height - this.w \/ 2;\r\n      this.vy *= friction;\r\n    } else if (this.y - this.w \/ 2 &lt; 0) {\r\n      this.y = this.w \/ 2;\r\n      this.vy *= friction;\r\n    }\r\n  }\r\n\r\n  display() {\r\n    ellipse(this.x, this.y, this.w, this.h);\r\n  }\r\n}<\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Problem: Many rooms in the basements of buildings are very depressing and hard to work in because of the lack of windows and access to nature. Looking out the window is a very comforting and calming action that one can take for granted when working on floors where there is plenty of access to windows. &hellip; <a href=\"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/?p=2518\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Visual Crit &#8211; Jud&#8221;<\/span><\/a><\/p>\n","protected":false},"author":30,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[20],"tags":[],"_links":{"self":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2518"}],"collection":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/users\/30"}],"replies":[{"embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=2518"}],"version-history":[{"count":4,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2518\/revisions"}],"predecessor-version":[{"id":2528,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=\/wp\/v2\/posts\/2518\/revisions\/2528"}],"wp:attachment":[{"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=2518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=2518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/courses.ideate.cmu.edu\/48-339\/s2022\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=2518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}