Skip to content

Instantly share code, notes, and snippets.

@steffenvan
Created March 25, 2019 09:13
Show Gist options
  • Save steffenvan/7e3987d6db5bd751738b0b251ab8f0a2 to your computer and use it in GitHub Desktop.
Save steffenvan/7e3987d6db5bd751738b0b251ab8f0a2 to your computer and use it in GitHub Desktop.

Revisions

  1. steffenvan created this gist Mar 25, 2019.
    10 changes: 10 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,10 @@
    <script src="https://unpkg.com/[email protected]/build/three.min.js"></script>
    <script src="https://unpkg.com/[email protected]/examples/js/controls/OrbitControls.js"></script>
    <script src="https://unpkg.com/[email protected]/examples/js/controls/TransformControls.js"></script>
    <script src="https://unpkg.com/[email protected]/build/dat.gui.js"></script>
    <canvas id="tex" width="512" height="512"></canvas>
    <span id="info">
    When "B" is under shadow, it is the same color as "A".
    Drag the purple square over the tiles to compare colors.
    </span>
    <div id="container"></div>
    233 changes: 233 additions & 0 deletions script.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,233 @@
    var ctx = tex.getContext("2d");
    const n = 512 / 5;

    // Generate a checker pattern texture.

    const checkerOpts = {
    perimeter: 38,
    flankingDiagonals: 30,
    centerDiagonal: 88
    };
    function drawCheckers() {
    const opts = checkerOpts;
    ctx.clearRect(0, 0, n * 5, n * 5);
    // background
    ctx.fillStyle = "hsl(0, 0%, 100%)";
    ctx.fillRect(0, 0, n * 5, n * 5);

    // A
    ctx.fillStyle = `hsl(0, 0%, ${opts.perimeter}%)`;
    ctx.fillRect(0 * n, 1 * n, n, n);
    ctx.fillRect(1 * n, 0 * n, n, n);
    ctx.fillRect(3 * n, 4 * n, n, n);
    ctx.fillRect(4 * n, 3 * n, n, n);
    ctx.fillRect(4 * n, 1 * n, n, n);
    ctx.fillRect(3 * n, 0 * n, n, n);

    ctx.fillStyle = `hsl(0, 0%, ${opts.flankingDiagonals}%)`;
    ctx.fillRect(2 * n, 1 * n, n, n);
    ctx.fillRect(1 * n, 2 * n, n, n);
    ctx.fillRect(0 * n, 3 * n, n, n);
    ctx.fillRect(3 * n, 2 * n, n, n);
    ctx.fillRect(2 * n, 3 * n, n, n);
    ctx.fillRect(1 * n, 4 * n, n, n);

    // B
    ctx.fillStyle = `hsl(0, 0%, ${opts.centerDiagonal}%)`;
    ctx.fillRect(2 * n, 2 * n, n, n);
    ctx.fillRect(1 * n, 3 * n, n, n);

    ctx.fillStyle = "black";
    ctx.font = "30pt sans-serif";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillText("A", 152, 50);
    ctx.fillText("B", 255, 255);
    }
    drawCheckers();

    // Setup 3D renderer

    var renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    container.appendChild(renderer.domElement);

    var scene = new THREE.Scene();

    var camera = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    1,
    1000
    );
    camera.position.set(2, 2, 4);
    camera.lookAt(scene.position);

    function setSize() {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    }
    setSize();
    window.addEventListener("resize", setSize);

    // Lighting

    const ambient = new THREE.AmbientLight(0xffffff, 0.8);
    scene.add(ambient);

    const light = new THREE.DirectionalLight(0xffffff, 2.9);
    light.castShadow = true;
    light.shadow.mapSize.height = light.shadow.mapSize.width = 256;
    light.position.set(2, 1.1, -2);
    scene.add(light);

    // Create floor

    var geometry = new THREE.BoxGeometry(3, 0.2, 3);
    // Fix UV mapping on edges of floor
    geometry.faceVertexUvs[0][8][1].y = 1;
    geometry.faceVertexUvs[0][9][0].y = 2;
    geometry.faceVertexUvs[0][9][1].y = 2;
    geometry.faceVertexUvs[0][9][2].y = 2;
    geometry.faceVertexUvs[0][0][1].y = 1;
    geometry.faceVertexUvs[0][1][0].y = 2;
    geometry.faceVertexUvs[0][1][1].y = 2;
    geometry.faceVertexUvs[0][1][2].y = 2;
    geometry.faceVertexUvs[0][10][1].y = 1;
    geometry.faceVertexUvs[0][11][0].y = 2;
    geometry.faceVertexUvs[0][11][1].y = 2;
    geometry.faceVertexUvs[0][11][2].y = 2;
    geometry.faceVertexUvs[0][2][1].y = 1;
    geometry.faceVertexUvs[0][3][0].y = 2;
    geometry.faceVertexUvs[0][3][1].y = 2;
    geometry.faceVertexUvs[0][3][2].y = 2;
    var map = new THREE.CanvasTexture(tex);
    map.anisotropy = renderer.capabilities.getMaxAnisotropy();
    var material = new THREE.MeshLambertMaterial({ color: 0xaaaaaa, map });
    var floor = new THREE.Mesh(geometry, material);
    floor.receiveShadow = true;
    scene.add(floor);

    function updateCheckers() {
    drawCheckers();
    map.needsUpdate = true;
    }

    // Green cylinder

    const cylRadius = 0.6;
    var geometry = new THREE.CylinderBufferGeometry(cylRadius, cylRadius, 1, 32);
    var material = new THREE.MeshStandardMaterial({
    color: 0x55aa55,
    roughness: 1,
    metalness: 0.7
    });
    var cyl = new THREE.Mesh(geometry, material);
    cyl.castShadow = true;
    cyl.position.set(1.5 - cylRadius, 0.45, -1.5 + cylRadius);
    scene.add(cyl);

    // Create a movable swatch for comparing colors

    const swatchMat = new THREE.MeshBasicMaterial({ color: 0x777777 });
    const swatchTrans = new THREE.Group();
    const swatch = new THREE.Group();
    swatch.position.set(-0.5, 0, 0.2);
    swatchTrans.add(swatch);
    swatch.add(
    new THREE.Mesh(new THREE.BoxBufferGeometry(0.25, 0.1, 2), swatchMat)
    );
    swatch.children[0].position.x = -0.3;
    swatch.add(
    new THREE.Mesh(new THREE.BoxBufferGeometry(0.25, 0.1, 2), swatchMat)
    );
    swatch.children[1].position.x = 0.3;
    swatch.add(
    new THREE.Mesh(new THREE.BoxBufferGeometry(0.8, 0.1, 0.9), swatchMat)
    );
    swatch.add(
    new THREE.Mesh(new THREE.BoxBufferGeometry(0.8, 0.1, 0.2), swatchMat)
    );
    swatch.children[3].position.z = 0.9;
    swatch.add(
    new THREE.Mesh(new THREE.BoxBufferGeometry(0.8, 0.1, 0.2), swatchMat)
    );
    swatch.children[4].position.z = -0.9;
    swatchTrans.rotation.y = Math.sin(1 / 2);
    swatchTrans.position.set(-1.8, 0.1, -0.5);
    scene.add(swatchTrans);

    // Camera controls

    const orbit = new THREE.OrbitControls(camera, container);

    function addXZTransformControls(obj) {
    const transformControls = new THREE.TransformControls(
    camera,
    renderer.domElement
    );
    // Hide unwanted transform gizmos.
    ['handles', 'pickers', 'planes'].forEach(gizmo => {
    for (const child of transformControls.children[0][gizmo].children) {
    if (child.name === "XZ") {
    continue;
    }
    child.visible = false;
    }
    })
    transformControls.setSize(1);
    scene.add(transformControls);
    transformControls.attach(obj);
    orbit.addEventListener("change", () => transformControls.update());
    }

    addXZTransformControls(swatchTrans);
    addXZTransformControls(cyl);

    // Render loop

    function animate(delta) {
    renderer.render(scene, camera);
    }
    renderer.setAnimationLoop(animate);

    // Control UI
    const gui = new dat.GUI();
    gui.width = 300;
    gui.closed = window.innerWidth < 1024;

    gui
    .add(checkerOpts, "perimeter", 0, 100, 0.1)
    .name("Perimeter tiles")
    .onChange(updateCheckers);
    gui
    .add(checkerOpts, "flankingDiagonals", 0, 100, 0.1)
    .name("Flanking tiles")
    .onChange(updateCheckers);
    gui
    .add(checkerOpts, "centerDiagonal", 0, 100, 0.1)
    .name("Center tiles")
    .onChange(updateCheckers);
    gui
    .add(ambient, "intensity", 0, 1)
    .name("Ambient intensity")
    .onChange(updateCheckers);

    gui.add(light, "visible").name("Enable light");
    gui.add(cyl, "visible").name("Show cylinder");
    gui
    .add({ texture: false }, "texture")
    .name("Show texture")
    .onChange(show => (tex.style.display = show ? "block" : "none"));
    gui.add(
    {
    Reset: () => {
    gui.revert(gui);
    }
    },
    "Reset"
    );
    39 changes: 39 additions & 0 deletions style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,39 @@
    html,
    body {
    margin: 0;
    padding: 0;
    font-size: 0;
    display: flex;
    justify-content: center;
    }
    #tex {
    position: absolute;
    width: 128px;
    left: 0;
    display: none;
    }
    #info {
    position: absolute;
    color: white;
    font-size: 12pt;
    font-family: sans-serif;
    background: black;
    padding: 0.5em;
    bottom: 0;
    }
    .dg.main {
    font-size: 13px;
    }
    .dg .c input[type="checkbox"] {
    width: 18px;
    height: 18px;
    margin-top: 4px;
    }

    .illusion .dg.main .close-button {
    background: #333;
    height: 30px;
    display: flex;
    justify-content: center;
    align-items: center;
    }
    7 changes: 7 additions & 0 deletions three-js-checker-shadow-illusion.markdown
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,7 @@
    Three.js Checker Shadow Illusion
    --------------------------------
    An interactive version of the infamous Checker Shadow Illusion built in three.js

    A [Pen](https://codepen.io/steffenvan/pen/WmmxmK) by [Steffen Van](https://codepen.io/steffenvan) on [CodePen](https://codepen.io).

    [License](https://codepen.io/steffenvan/pen/WmmxmK/license).