What’s on our mind?

Collection of articles, design, site, and resources made by designers and publisher @Menu View

index87
class Visualization {
    constructor() {
        this.renderer = new THREE.WebGLRenderer({antialias: true});
        this.renderer.shadowMap.enabled = true;
        container.appendChild(this.renderer.domElement);

        this.scene = new THREE.Scene();
        this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
        this.fakeCamera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
        this.fakeCamera.up.set(0, -1, 0);
        this.camera.position.z = this.fakeCamera.position.z = 33;

        this.glassGroup = new THREE.Group();

        this.orbit = new OrbitControls(this.fakeCamera, this.renderer.domElement);
        this.orbit.enableZoom = false;
        this.orbit.enableDamping = true;
        this.orbit.autoRotate = true;

        this.setupScene();
        this.createBackgroundMaterial();
        this.createGlass();
        this.render();
    }

    setupScene() {
        this.scene.background = new THREE.Color(0xffffff);

        const pointLightShadow = new THREE.PointLight(0xffffff, .3);
        pointLightShadow.castShadow = true;
        pointLightShadow.shadow.mapSize.width = 1500;
        pointLightShadow.shadow.mapSize.height = 1500;
        pointLightShadow.position.set(0, 40, 0);
        this.scene.add(pointLightShadow);

        const pointLight = new THREE.PointLight(0xffffff, .9);
        pointLight.position.set(10, 0, 10);
        this.scene.add(pointLight);

        this.backGroundCanvas = document.createElement('canvas');
        this.backPlaneMesh = new THREE.Mesh(this.generateFullWidthPlaneGeometry(), this.createBackgroundMaterial());
        this.backPlaneMesh.position.z = -10;
        this.scene.add(this.backPlaneMesh);

        const bottomPlaneGeometry = new THREE.PlaneBufferGeometry(100, 100, 1, 1);
        this.bottomPlaneMesh = new THREE.Mesh(bottomPlaneGeometry, new THREE.ShadowMaterial({opacity: .02}));
        this.bottomPlaneMesh.rotateX(-.5 * Math.PI);
        this.bottomPlaneMesh.position.y = -10;
        this.bottomPlaneMesh.receiveShadow = true;
        this.scene.add(this.bottomPlaneMesh);
    }

    generateFullWidthPlaneGeometry() {
        return new THREE.PlaneBufferGeometry(38 * window.innerWidth / window.innerHeight, 38, 1, 1);
    }

    createBackgroundMaterial() {
        const canvasWidth = this.backGroundCanvas.width = 3000 * window.innerWidth / window.innerHeight;
        const canvasHeight = this.backGroundCanvas.height = 3000;
        const ctx = this.backGroundCanvas.getContext('2d');

        if (!glassConfig.showTexture) {
            ctx.rect(0, 0, canvasWidth, canvasHeight);
            ctx.fillStyle = '#ffffff';
            ctx.fill();
            ctx.fillStyle = '#000000';
            const fontSize = .08 * canvasWidth;
            ctx.font = fontSize + "px GmarketSans";
            ctx.textAlign = 'center';
            ctx.fillText("webstoryboy", .5 * canvasWidth, .5 * canvasHeight);
        } else {
            const w = Math.round(canvasWidth / 15);
            const numCol = Math.ceil(canvasWidth / w);
            const numRow = Math.ceil(canvasHeight / w);
            const constColors = ["#F06449", "#EDE6E3", "#DADAD9", "#36382E", "#5BC3EB"];
            for (let j = 0; j < numRow; j++) {
                for (let i = 0; i < numCol; i++) {
                    const shuffledArr = constColors.map((a) => [Math.random(), a]).sort((a, b) => a[0] - b[
                        0]).map((a) => a[1]);
                    ctx.fillStyle = shuffledArr[0];
                    ctx.fillRect(w * i, w * j, w, w);
                    if (Math.random() > .5) {
                        ctx.beginPath();
                        ctx.arc(w * i, w * j, .5 * w, 0, .5 * Math.PI, false);
                        ctx.lineTo(w * i, w * j);
                        ctx.closePath();
                        ctx.fillStyle = shuffledArr[1];
                        ctx.fill();
                        ctx.beginPath();
                        ctx.arc(w * (i + 1), w * (j + 1), .5 * w, 0, 1.5 * Math.PI, false);
                        ctx.lineTo(w * (i + 1), w * (j + 1));
                        ctx.closePath();
                        ctx.fillStyle = shuffledArr[2];
                        ctx.fill();
                    } else {
                        ctx.beginPath();
                        ctx.arc(w * (i + 1), w * j, .5 * w, .5 * Math.PI, 1.5 * Math.PI, false);
                        ctx.lineTo(w * (i + 1), w * j);
                        ctx.closePath();
                        ctx.fillStyle = shuffledArr[1];
                        ctx.fill();
                        ctx.beginPath();
                        ctx.arc(w * i, w * (j + 1), .5 * w, 1.5 * Math.PI, 0, false);
                        ctx.lineTo(w * i, w * (j + 1));
                        ctx.closePath();
                        ctx.fillStyle = shuffledArr[2];
                        ctx.fill();
                    }
                }
            }
        }
        const shadowTexture = new THREE.CanvasTexture(this.backGroundCanvas);
        return new THREE.MeshBasicMaterial({
            map: shadowTexture
        });
    }

    createGlass() {
        this.materals = [];
        const glassMaterial = new THREE.MeshPhysicalMaterial({
            metalness: glassConfig.metalness,
            roughness: glassConfig.roughness,
            transmission: glassConfig.transmission,
            thickness: glassConfig.materialThickness,
            transparent: true,
            emissive: new THREE.Color(0x3C4444),
            opacity: glassConfig.opacity,
        });
        this.materals.push(glassMaterial);

        const glassMaterialDSide = glassMaterial.clone();
        glassMaterialDSide.side = THREE.DoubleSide;
        this.materals.push(glassMaterialDSide);

        const glassMaterialFlatDSide = glassMaterial.clone();
        glassMaterialFlatDSide.flatShading = true;
        glassMaterialFlatDSide.side = THREE.DoubleSide;
        this.materals.push(glassMaterialFlatDSide);

        const glassMaterialNoDepth = glassMaterial.clone();
        glassMaterialNoDepth.depthTest = false;
        this.materals.push(glassMaterialNoDepth);

        this.glassMeshes = {
            outerFaces: new THREE.Mesh(this.generateOuterFacedCylinder(), glassMaterialFlatDSide),
            topCylinder: new THREE.Mesh(this.generateOuterTopCylinder(), glassMaterialDSide),
            bottom: new THREE.Mesh(this.generateBottom(), glassMaterialDSide),
            innerSurface: new THREE.Mesh(this.generateInnerSurface(), glassMaterialNoDepth),
            topRing: new THREE.Mesh(this.generateTopRing(), glassMaterialDSide)
        };

        this.glassMeshes.outerFaces.renderOrder = 2;
        this.glassMeshes.topCylinder.renderOrder = 2;
        this.glassMeshes.bottom.renderOrder = 2;

        this.glassMeshes.outerFaces.castShadow = true;
        this.glassMeshes.bottom.castShadow = true;

        const keys = Object.keys(this.glassMeshes);
        keys.forEach((key) => {
            this.glassGroup.add(this.glassMeshes[key]);
        });

        this.scene.add(this.glassGroup);
    }

    generateOuterFacedCylinder() {
        const points = [];
        points.push(new THREE.Vector2(glassConfig.bottomRadius, glassConfig.glassFullHeight * glassConfig
            .bottomHeightPercentage));
        points.push(new THREE.Vector2(glassConfig.topRadius, glassConfig.glassFullHeight - glassConfig
            .glassFullHeight * glassConfig.topHeightPercentage));
        points.forEach(p => p.y -= glassConfig.glassPosition * glassConfig.glassFullHeight);

        return new THREE.LatheGeometry(points, glassConfig.outerFaceNumber);
    }

    generateBottom() {
        const points = [];
        points.push(new THREE.Vector2(0, 0));
        points.push(new THREE.Vector2(glassConfig.bottomRadius - .9, 0));
        points.push(new THREE.Vector2(glassConfig.bottomRadius - .8, glassConfig.bottomNotchDepth));
        points.push(new THREE.Vector2(glassConfig.bottomRadius - .4, glassConfig.bottomNotchDepth));
        points.push(new THREE.Vector2(glassConfig.bottomRadius - .3, 0));
        points.push(new THREE.Vector2(glassConfig.bottomRadius, 0));
        points.push(new THREE.Vector2(glassConfig.bottomRadius, glassConfig.glassFullHeight * glassConfig
            .bottomHeightPercentage));
        points.push(new THREE.Vector2(.9 * glassConfig.bottomRadius, glassConfig.glassFullHeight *
            glassConfig.bottomHeightPercentage + glassConfig.bottomRadius * .05));
        points.forEach(p => p.y -= (glassConfig.glassPosition * glassConfig.glassFullHeight));

        return new THREE.LatheGeometry(points, glassConfig.circleSegments);
    }

    generateOuterTopCylinder() {
        const overlay = .05;
        const points = [];
        points.push(new THREE.Vector2(glassConfig.topRadius * .9, glassConfig.glassFullHeight - glassConfig
            .glassFullHeight * glassConfig.topHeightPercentage - glassConfig.topRadius * .05));
        points.push(new THREE.Vector2(glassConfig.topRadius, glassConfig.glassFullHeight - glassConfig
            .glassFullHeight * glassConfig.topHeightPercentage));
        points.push(new THREE.Vector2(glassConfig.topRadius, glassConfig.glassFullHeight + overlay));
        points.forEach(p => p.y -= (glassConfig.glassPosition * glassConfig.glassFullHeight + overlay));

        return new THREE.LatheGeometry(points, glassConfig.circleSegments);
    }

    generateInnerSurface() {
        const points = [];
        points.push(new THREE.Vector2(glassConfig.topRadius - glassConfig.thickness, glassConfig
            .glassFullHeight));
        points.push(new THREE.Vector2(glassConfig.bottomRadius - glassConfig.thickness, glassConfig
            .bottomExtraThickness * glassConfig.thickness));
        points.push(new THREE.Vector2(0, glassConfig.bottomExtraThickness * glassConfig.thickness));
        points.forEach(p => p.y -= glassConfig.glassPosition * glassConfig.glassFullHeight);

        return new THREE.LatheGeometry(points, glassConfig.circleSegments);
    }

    generateTopRing() {
        const geometry = new THREE.RingGeometry(glassConfig.topRadius, glassConfig.topRadius - glassConfig
            .thickness, glassConfig.circleSegments, 1);
        geometry.translate(0, 0, (glassConfig.glassPosition - 1) * glassConfig.glassFullHeight);
        geometry.rotateX(Math.PI / 2);
        return geometry;
    }

    updateGeometry(mesh, geometry) {
        mesh.geometry.dispose();
        mesh.geometry = geometry;
    }

    render() {
        this.orbit.update();
        this.glassGroup.lookAt(this.fakeCamera.position);
        this.renderer.render(this.scene, this.camera);
    }

    loop() {
        this.render();
        requestAnimationFrame(this.loop.bind(this));
    }

    updateSize() {
        this.camera.aspect = this.fakeCamera.aspect = window.innerWidth / window.innerHeight;
        this.camera.updateProjectionMatrix();
        this.fakeCamera.updateProjectionMatrix();
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
        this.updateGeometry(this.backPlaneMesh, this.generateFullWidthPlaneGeometry());
        this.backPlaneMesh.material = this.createBackgroundMaterial();
    }
}

const controls = new Controls();
const visualization = new Visualization();

visualization.updateSize();

window.addEventListener('resize', () => visualization.updateSize());
visualization.loop();