This week, I spent time planning a function that could be used to position objects in the room. For example, I might want to place two cubes around a plant, but they should never overlap with the plant or each other. When trying to understand this problem, I found this video by Daniel Shiffman very useful. That combined with pseudocode that Nikolai provided was the key to create some functions.

The pseudocode:

for (let i = 0; i < max; i++) {
  outer: for (;;) {
    let point = make_random_point();
    for (let j = 0; j < i; j++) {
      if (dist(locations[j], point) < limit) {
        continue outer;
      }
    }
    locations.push(point);
    break;
  }
}

We wanted to create arrays for the possible locations of objects, either around a circle or semicircle. There is a central object with some orbiting objects:

const locationsCircle = [];
const locationsHalfCircle = [];

function neighbour_location_circle({ cx, cy, cz, min, max, startAngle = 0, endAngle = 2 * Math.PI }) {
  // distance from the object in the center
  const distance = min + (max - min) * Math.random();
  const angle = startAngle + (endAngle - startAngle) * Math.random();
  return [cx + distance * Math.cos(angle), cy, cz + distance * Math.sin(angle)];
}

// how close to each other the orbiting objects can be
let limit = 6;
// number of positions to create
let numOfLoc = 3;

function dist(x1, x2, y1, y2) {
  var a = x1 - x2;
  var b = y1 - y2;
  return Math.sqrt(a * a + b * b);
}

Creating positions for orbiting objects by using limit and checking that they don't overlap:

for (let i = 0; i < numOfLoc; i++) {
  outer: for (;;) {
    let point = neighbour_location_circle({
      cx: xLoc,
      cy: yLoc,
      cz: 5,
      min: 15,
      max: 25
    });
    for (let j = 0; j < i; j++) {
      if (dist(locationsCircle[j][0], point[0], locationsCircle[j][2], point[2]) < limit) {
        continue outer;
      }
    }
    locationsCircle.push(point);
    break;
  }
}

// now we add spheres that orbit the box (as a circle)
for (let i = 0; i < numOfLoc; i++) {
  const ballGeometry = new THREE.SphereGeometry(5, 5, 5);
  const material = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    emissive: 0x444444
  });
  const ball = new THREE.Mesh(ballGeometry, material);
  ball.position.x = locationsCircle[i][0];
  ball.position.y = locationsCircle[i][1];
  ball.position.z = locationsCircle[i][2];
  scene.add(ball);
}

For placing the objects in a semicircle (so that they don't end up inside a wall), we modify the start and end angle in the neighbour_location_circle function:

for (let i = 0; i < numOfLoc; i++) {
  outer: for (;;) {
    let point = neighbour_location_circle({
      cx: 45,
      cy: 5,
      cz: 5,
      min: 15,
      max: 25,
      startAngle: Math.PI / 2,
      endAngle: (3 / 2) * Math.PI
    });
    for (let j = 0; j < i; j++) {
      if (dist(locationsCircle[j][0], point[0], locationsCircle[j][2], point[2]) < limit) {
        continue outer;
      }
    }
    locationsHalfCircle.push(point);
    break;
  }
}

Adding New Data Points

It was also time to make the room visuals a bit more advanced. One of the elements of the room is a big blob that floats in the middle – a bit like a brain inside a head. Luckily, when working on my own art project on my free time, I had bumped into this resource about deformations with Perlin noise.

This animated blob is based on a vertex shader and fragment shader, added with a texture image. It's a good example on how to use normals and materials in deformations. An interesting thing with textures is that they can add a whole other dimension to the visual: you can use any image as a texture, and the shape will then reflect that image, hinting at a space or object that exists outside the viewport.

Finally, I spent time on the Python side of the app, adding a bunch of new variables that are based on the NLP calculations. The values that client side receives are now:

  • avgTone
  • avgSubj
  • numOfMe (number of "I" and "me" words)
  • avgSentenceLength
  • numOfUnique (number of unique words)
  • interactionAmount (number of @ tags)
  • numOfNeg (number of negative tweets)
  • numOfPos
  • numOfNeutr
  • numOfAll

I always enjoy exploring how easily Python can handle data. For example, I was prepared for requiring multiple lines of code to extract all the @ tags from an account. Finally it was just:

interactions = re.findall(r'@[\w\.-]+',str(whole_text_sample))
numOfInteractions = len(interactions)