Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                
              
            
!

CSS

              
                body {
  margin: 0;
  background-color: #000;
  color: #fff;
  font-family: Monospace;
  font-size: 13px;
  line-height: 24px;
  overscroll-behavior: none;
}

a {
  color: #ff0;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

button {
  cursor: pointer;
  text-transform: uppercase;
}

#info {
  position: absolute;
  top: 0px;
  width: 100%;
  padding: 10px;
  box-sizing: border-box;
  text-align: center;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  pointer-events: none;
  z-index: 1; /* TODO Solve this in HTML */
}

a,
button,
input,
select {
  pointer-events: auto;
}

.dg.ac {
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  z-index: 2 !important; /* TODO Solve this in HTML */
}

#overlay {
  position: absolute;
  z-index: 2;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.7);
}

#overlay button {
  background: #ffffff;
  border: 0;
  color: #000000;
  padding: 16px 20px;
  text-transform: uppercase;
  cursor: pointer;
}

#notSupported {
  width: 50%;
  margin: auto;
  background-color: #f00;
  margin-top: 20px;
  padding: 10px;
}

              
            
!

JS

              
                import * as THREE from "https://cdn.skypack.dev/three@0.123.0";
import { OrbitControls } from "https://cdn.skypack.dev/three@0.123.0/examples/jsm/controls/OrbitControls";

import { GUI } from "https://cdn.skypack.dev/three@0.123.0/examples/jsm/libs/dat.gui.module.js";

let camera, renderer;
let planes, planeObjects, planeHelpers;
let clock;

/* Decolonialization Variables */
let homecoming, decolonialsMascot;
var startDelayIsSet = false;

/* DO NOT CHANGE THESE DEFAULT VALUES */

/* GUI PARAMS */

// const params = {
//   planeX: {
//     constant: 0,
//     negated: false,
//     displayHelper: false
//   },
//   planeY: {
//     constant: 0,
//     negated: false,
//     displayHelper: false
//   },
//   planeZ: {
//     constant: 0,
//     negated: false,
//     displayHelper: false
//   }
// };

const knotParams = {
  planeXConstant: 1,
  planeYConstant: 1,
  planeZConstant: 1
};

const settlerControlColor = 0xfff; // white, TJ school color
const landColor = 0xffffff; // blue, TJ school color
const DEFAULT_AMBIENT_LIGHT_COLOR = 0xffffff;
const DEFAULT_DIRECTIONAL_LIGHT_COLOR = 0xffffff;
const DEFAULT_ROTATION_X = 0.5;
const DEFAULT_ROTATION_Y = 0.2;

var decolonializationIsUnderway = false;

/* CHANGEABLE PARAMS:
   colorParams
   decolonizationParams
*/

const colorParams = {
  innerKnotColor: settlerControlColor,
  outerKnotColor: landColor,
  ambientLightColor: DEFAULT_AMBIENT_LIGHT_COLOR,
  directionalLightColor: DEFAULT_DIRECTIONAL_LIGHT_COLOR
};

const decolonizationParams = {
  // to decolonize upon load, change to "true"
  decolonialize: false,
  // time between start of rotation and decolonialization
  startDelayMilliseconds: 5000,
  // amount of land abolished as property in each rotation step
  propertyAbolitionRate: 0.003,
  // size of rotation step, i.e., speed, a value > 1 is ill-advised
  x: DEFAULT_ROTATION_X,
  // size of rotation step, i.e., speed, a value > 1 is ill-advised
  y: DEFAULT_ROTATION_Y
};

init();
decolonialize();

function createPlaneStencilGroup(geometry, plane, renderOrder) {
  const group = new THREE.Group();
  const baseMat = new THREE.MeshBasicMaterial();
  baseMat.depthWrite = false;
  baseMat.depthTest = false;
  baseMat.colorWrite = false;
  baseMat.stencilWrite = true;
  baseMat.stencilFunc = THREE.AlwaysStencilFunc;

  // back faces
  const mat0 = baseMat.clone();
  mat0.side = THREE.BackSide;
  mat0.clippingPlanes = [plane];
  mat0.stencilFail = THREE.IncrementWrapStencilOp;
  mat0.stencilZFail = THREE.IncrementWrapStencilOp;
  mat0.stencilZPass = THREE.IncrementWrapStencilOp;

  const mesh0 = new THREE.Mesh(geometry, mat0);
  mesh0.renderOrder = renderOrder;
  group.add(mesh0);

  // front faces
  const mat1 = baseMat.clone();
  mat1.side = THREE.FrontSide;
  mat1.clippingPlanes = [plane];
  mat1.stencilFail = THREE.DecrementWrapStencilOp;
  mat1.stencilZFail = THREE.DecrementWrapStencilOp;
  mat1.stencilZPass = THREE.DecrementWrapStencilOp;

  const mesh1 = new THREE.Mesh(geometry, mat1);
  mesh1.renderOrder = renderOrder;

  group.add(mesh1);

  return group;
}

function torusKnotGeometry(
  landArea,
  indigenousNations,
  currentPopulation,
  maxIndigenousPortionOfCurrentPopulation,
  colonialisms,
  categories
) {
  const landToTorusRadius = landArea / 100000;
  const indigenousNationsToTubeRadius = indigenousNations.length / 100;
  const currentPopulationToTubularSegments = currentPopulation / 10000;
  const maxCurrentIndigenousPopulationToRadialSegments =
    currentPopulationToTubularSegments *
    maxIndigenousPortionOfCurrentPopulation;

  return new THREE.TorusKnotBufferGeometry(
    landToTorusRadius,
    indigenousNationsToTubeRadius,
    currentPopulationToTubularSegments,
    maxCurrentIndigenousPopulationToRadialSegments,
    colonialisms.length,
    categories.length
  );
}

function init() {
  clock = new THREE.Clock();

  homecoming = new THREE.Scene();

  camera = new THREE.PerspectiveCamera(
    36,
    window.innerWidth / window.innerHeight,
    1,
    100
  );
  camera.position.set(2, 2, 2);

  homecoming.add(new THREE.AmbientLight(colorParams.ambientLightColor, 0.5));

  const dirLight = new THREE.DirectionalLight(
    colorParams.directionalLightColor,
    1
  );
  dirLight.position.set(5, 10, 7.5);
  dirLight.castShadow = true;
  dirLight.shadow.camera.right = 2;
  dirLight.shadow.camera.left = -2;
  dirLight.shadow.camera.top = 2;
  dirLight.shadow.camera.bottom = -2;

  dirLight.shadow.mapSize.width = 1024;
  dirLight.shadow.mapSize.height = 1024;
  homecoming.add(dirLight);

  planes = [
    new THREE.Plane(new THREE.Vector3(-1, 0, 0), knotParams.planeXConstant),
    new THREE.Plane(new THREE.Vector3(0, -1, 0), knotParams.planeYConstant),
    new THREE.Plane(new THREE.Vector3(0, 0, -1), knotParams.planeZConstant)
  ];

  planeHelpers = planes.map((p) => new THREE.PlaneHelper(p, 2, 0xffffff));
  planeHelpers.forEach((ph) => {
    ph.visible = false;
    homecoming.add(ph);
  });

  /* colonialisms and categories should be coprime */
  const colonialisms = ["external", "internal"];
  const categories = ["settler", "native", "slave"];

  const virginiaInSquareMiles = 42774.2;
  const indigenousNations = [
    "Patawomeck",
    "Nottoway",
    "Cheroenhaka (Nottoway)",
    "Monacan",
    "Nansemond",
    "Upper Mattaponi",
    "Rappahannock",
    "Chickahominy Indians Eastern Division",
    "Chickahominy",
    "Pamunkey",
    "Mattaponi"
  ];
  const currentPopulation = 9546958;
  const maxIndigenousPortionOfCurrentPopulation = 0.05;

  const settlerColonizationGeometry = torusKnotGeometry(
    virginiaInSquareMiles,
    indigenousNations,
    currentPopulation,
    maxIndigenousPortionOfCurrentPopulation,
    colonialisms,
    categories
  );

  decolonialsMascot = new THREE.Group();
  homecoming.add(decolonialsMascot);

  // Set up clip plane rendering
  planeObjects = [];
  const planeGeom = new THREE.PlaneBufferGeometry(4, 4);

  for (let i = 0; i < 3; i++) {
    const poGroup = new THREE.Group();
    const plane = planes[i];
    const stencilGroup = createPlaneStencilGroup(
      settlerColonizationGeometry,
      plane,
      i + 1
    );

    // plane is clipped by the other clipping planes
    const planeMat = new THREE.MeshStandardMaterial({
      color: colorParams.innerKnotColor,
      metalness: 0.1,
      roughness: 0.75,
      clippingPlanes: planes.filter((p) => p !== plane),

      stencilWrite: true,
      stencilRef: 0,
      stencilFunc: THREE.NotEqualStencilFunc,
      stencilFail: THREE.ReplaceStencilOp,
      stencilZFail: THREE.ReplaceStencilOp,
      stencilZPass: THREE.ReplaceStencilOp
    });
    const po = new THREE.Mesh(planeGeom, planeMat);
    po.onAfterRender = function (renderer) {
      renderer.clearStencil();
    };

    po.renderOrder = i + 1.1;

    decolonialsMascot.add(stencilGroup);
    poGroup.add(po);
    planeObjects.push(po);
    homecoming.add(poGroup);
  }

  const material = new THREE.MeshStandardMaterial({
    color: colorParams.outerKnotColor,
    metalness: 0.1,
    roughness: 0.75,
    clippingPlanes: planes,
    clipShadows: true,
    shadowSide: THREE.DoubleSide
  });

  // add the color
  const clippedColorFront = new THREE.Mesh(
    settlerColonizationGeometry,
    material
  );
  clippedColorFront.castShadow = true;
  clippedColorFront.renderOrder = 6;
  decolonialsMascot.add(clippedColorFront);

  const ground = new THREE.Mesh(
    new THREE.PlaneBufferGeometry(9, 9, 1, 1),
    new THREE.ShadowMaterial({
      color: 0,
      opacity: 0.25,
      side: THREE.DoubleSide
    })
  );

  ground.rotation.x = -Math.PI / 2; // rotates X/Y to X/Z
  ground.position.y = -1;
  ground.receiveShadow = true;
  homecoming.add(ground);

  // Renderer
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.shadowMap.enabled = true;
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.setClearColor(0x263238);
  window.addEventListener("resize", onWindowResize, false);
  document.body.appendChild(renderer.domElement);

  renderer.localClippingEnabled = true;

  // Controls
  const controls = new OrbitControls(camera, renderer.domElement);
  controls.minDistance = 2;
  controls.maxDistance = 20;
  controls.update();

  // GUI
  const gui = new GUI();
  gui.add(decolonizationParams, "decolonialize");
  gui.add(decolonizationParams, "propertyAbolitionRate").name("abolition");

  // const planeX = gui.addFolder("planeX");
  // planeX
  //   .add(params.planeX, "displayHelper")
  //   .onChange((v) => (planeHelpers[0].visible = v));
  // planeX
  //   .add(params.planeX, "constant")
  //   .min(-1)
  //   .max(1)
  //   .onChange((d) => (planes[0].constant = d));
  // planeX.add(params.planeX, "negated").onChange(() => {
  //   planes[0].negate();
  //   params.planeX.constant = planes[0].constant;
  // });
  // planeX.open();

  // const planeY = gui.addFolder("planeY");
  // planeY
  //   .add(params.planeY, "displayHelper")
  //   .onChange((v) => (planeHelpers[1].visible = v));
  // planeY
  //   .add(params.planeY, "constant")
  //   .min(-1)
  //   .max(1)
  //   .onChange((d) => (planes[1].constant = d));
  // planeY.add(params.planeY, "negated").onChange(() => {
  //   planes[1].negate();
  //   params.planeY.constant = planes[1].constant;
  // });
  // planeY.open();

  // const planeZ = gui.addFolder("planeZ");
  // planeZ
  //   .add(params.planeZ, "displayHelper")
  //   .onChange((v) => (planeHelpers[2].visible = v));
  // planeZ
  //   .add(params.planeZ, "constant")
  //   .min(-1)
  //   .max(1)
  //   .onChange((d) => (planes[2].constant = d));
  // planeZ.add(params.planeZ, "negated").onChange(() => {
  //   planes[2].negate();
  //   params.planeZ.constant = planes[2].constant;
  // });
  // planeZ.open();
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();

  renderer.setSize(window.innerWidth, window.innerHeight);
}

function decolonialize() {
  const delta = clock.getDelta();

  const frameId = requestAnimationFrame(decolonialize);
  if (decolonizationParams.decolonialize) {
    decolonialsMascot.rotation.x += delta * decolonizationParams.x;
    decolonialsMascot.rotation.y += delta * decolonizationParams.y;

    if (!startDelayIsSet) {
      setTimeout(
        abolishLandAsProperty,
        decolonizationParams.startDelayMilliseconds,
        frameId
      );

      startDelayIsSet = true;
    } else if (decolonializationIsUnderway) {
      abolishLandAsProperty(frameId);
    }
  }

  for (let i = 0; i < planeObjects.length; i++) {
    const plane = planes[i];
    const po = planeObjects[i];
    plane.coplanarPoint(po.position);
    po.lookAt(
      po.position.x - plane.normal.x,
      po.position.y - plane.normal.y,
      po.position.z - plane.normal.z
    );
  }

  renderer.render(homecoming, camera);
}

function abolishLandAsProperty(id) {
  const plane = planes[Math.floor(Math.random() * planes.length)];
  const constant = plane.constant;
  plane.constant =
    constant > -1 ? constant - decolonizationParams.propertyAbolitionRate : -1;
  console.log(plane.constant);
  if (plane.constant == -1) cancelAnimationFrame(id);

  decolonializationIsUnderway = true;
}

              
            
!
999px

Console