Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ 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

Save Automatically?

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

              
                <div id="container"></div>
<script src="https://threejs.org/build/three.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
<script src="https://threejs.org/examples/js/renderers/SVGRenderer.js"></script>
<script src="https://threejs.org/examples/js/renderers/Projector.js"></script>
              
            
!

CSS

              
                body {
  color: #000;
  font-family: Monospace;
  font-size: 13px;
  text-align: center;
  font-weight: bold;

  background-color: #fff;
  margin: 0px;
}

#container {
  margin-top: 50px;
  margin-left: auto;
  margin-right: auto;
  overflow: hidden;
  box-sizing: content-box;
  border: 1px solid green;
  width: 50%;
}

#container>canvas {
  overflow: hidden;
  outline: none;
  border: none;
}
#container>svg {
  overflow: hidden;
  outline: none;
  border: none;
}
              
            
!

JS

              
                // Which renderer?
// CHANGE ME
var useSVGRenderer = true;


var container, camera, controls, scene, renderer;

// Settings
var levelAndWallColor = 0x0000ff;
var wallOpacity = 0.25;
var edgeColor = 0xff0000;
var edgeOpacity = 1.0;
var frustumWidth = 250;
var selectionBox = new THREE.Box3();

// Debug help
var enableMouseControl = true;

init();

if(useSVGRenderer) {
	animateLoop();
} else {
	renderer.setAnimationLoop(animate);
}

function init() {
	// Renderer
	if(useSVGRenderer) {
		renderer = new THREE.SVGRenderer();
		renderer.setClearColor(0xffffff, 0);	
	} else {
		renderer = new THREE.WebGLRenderer({alpha: true, antialias:true});
		renderer.setPixelRatio(window.devicePixelRatio);
		renderer.setClearColor(0xffffff, 0);
		renderer.autoClear = false;
	}

    container = document.getElementById("container");
    container.appendChild(renderer.domElement);

	// Scene
	scene = new THREE.Scene();

  // Animation clock and mixer
	clock = new THREE.Clock();

	// Geometry
	var levels = 5;
    var shapePoints = getAcutePoints(levels);
	doExtrudedMeshAndLine(shapePoints, 10);

	// Ortho camera with isometric view with Z up
	var frustrum = getFrustrum();
	camera = new THREE.OrthographicCamera(frustrum.left, frustrum.right, frustrum.top, frustrum.bottom, 0, 1000);
	camera.up.set(0, 0, 1);
	camera.rotation.order = 'XYZ';
    camera.position.set(300, -300, 300);
	camera.lookAt(scene.position); // or the origin
	scene.add(camera);

    // Pan/tilt/zoom controls
    controls = new THREE.OrbitControls(camera, renderer.domElement);
	controls.enablePan = enableMouseControl;
	controls.enableRotate = enableMouseControl;
	controls.enableZoom = enableMouseControl;

	// Center the scene
	recalcFrustrumWidthForSelection(scene.children);
}

// An acute trapezoid shape
function getAcutePoints(levels) {
	var points = [];
	points.push(new THREE.Vector2(  0,  0));
	points.push(new THREE.Vector2(  0,  100));
	points.push(new THREE.Vector2(  50, 80));
	points.push(new THREE.Vector2(  50, 0));
	points.push(new THREE.Vector2(  0,  0));

	var allPoints = [];
	for(var i = 0; i < levels; ++i) {
		allPoints.push(points);
	}

	return allPoints;
}

// Extrudes a shape geometry in to multiple stacked shapes
function doExtrudedMeshAndLine(data, levelHeight) {
	// Create a group for this shape
	var stackedShape = new THREE.Group();

	// Draw each level
	for(var level = 0; level < data.length; level++) {
		var shape = new THREE.Shape(data[level]);

		var extrusionSettings = {
			depth: levelHeight,
			steps: 1,
			curveSegments: 6,
			bevelEnabled: false
		};
		var geometry = new THREE.ExtrudeBufferGeometry(shape, extrusionSettings);

		// ExtrudeBufferGeometry splits the geometry in to lid faces and
		// side faces, where the lid faces are group 0. All we do here is split first group
		// in half, as the first half of it is the original shape (our "level") and the second
		//  half is the top copy of the original shape (our "ceiling").
		var halfLidGroup = geometry.groups[0].count / 2;
		geometry.addGroup(geometry.groups[0].start + halfLidGroup, halfLidGroup, 2);
		geometry.groups[0].count /= 2;

		var opacity = (wallOpacity / data.length) * (data.length - level - 1);
		var materialFloor = new THREE.MeshBasicMaterial({ color: levelAndWallColor, side: THREE.DoubleSide, transparent: true, opacity: opacity });
		var materialWalls = new THREE.MeshBasicMaterial({ color: levelAndWallColor, side: THREE.DoubleSide, transparent: true, opacity: opacity });
		var materialCeiling = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide, transparent: true, opacity: 0.0 });
		var materialArray = [ materialFloor, materialWalls, materialCeiling ];

	    // Walls/floor/Ceiling
	    var mesh = new THREE.Mesh(geometry, materialArray);
		mesh.position.set(0, 0, level * levelHeight);

		// Full box wireframe
		var levelGeo = new THREE.ShapeGeometry(shape);
	    var geo = new THREE.EdgesGeometry(mesh.geometry);
	    var mat = new THREE.LineBasicMaterial({ color: edgeColor, linewidth: 1, transparent: true, opacity: edgeOpacity });
		var wireframe = new THREE.LineSegments(geo, mat);
		mesh.add(wireframe);

		// Add this level to the stackedShape
		stackedShape.add(mesh);
	}

	// Add the stackedShape to the scene
	scene.add(stackedShape);
}

function recalcFrustrumWidthForSelection(selection, fitOffset = 1.2) {
	// Get a box that contains the entire selection
	selectionBox = new THREE.Box3();
	for(const object of selection) {
		// Skip lights and cameras
		if(object.type.indexOf("Camera") !== -1 || object.type.indexOf("Light") !== -1) {
			continue;
		}
		selectionBox.expandByObject(object);
	}

	// Get the center of the selection's bounding box and let
	// OrbitControls do the heavy lifting of moving the OrthographicCamera
	var selCenter = selectionBox.getCenter(new THREE.Vector3());
	controls.target = selCenter;
    controls.update();

	// FrustrumWidth is calculated as roughly the x/y hypotenuse across the middle of Z
	// which is approximately correct given the Z-up isometric orientation we use...
	var boxAboutZ = selectionBox.clone();
	boxAboutZ.min.z = boxAboutZ.max.z = selCenter.z;
	frustumWidth = boxAboutZ.min.distanceTo(boxAboutZ.max) * fitOffset;
}

function getFrustrum() {
	// If the CSS doesn't specify widths, container.clientWidth might be 0
	// on the other hand, getBoundingClientRect() includes all borders/padding/outlines
	// which also screws us... For now we'll just expect the CSS to be set right
	//var containerBox = container.getBoundingClientRect();

	// Get the content height in pixel coordinates
	var renderSize;
	if(useSVGRenderer) {
		var containerBox = container.getBoundingClientRect();
		renderSize = new THREE.Vector2(containerBox.width, containerBox.height);
	} else {
		renderSize = renderer.getSize(new THREE.Vector2());
	}
	var contentHeight = renderSize.height * window.devicePixelRatio;
	if(camera) {
		let minY = Math.abs((contentHeight / 2) * selectionBox.min.clone().project(camera).y);
		let maxY = Math.abs((contentHeight / 2) * selectionBox.max.clone().project(camera).y);
		contentHeight = Math.floor((minY + maxY) * 1.5);
	}

	// Calculate the frustrum, canvas width forces height (height can scroll)...
	var frustrum = {
		width: Math.floor(container.clientWidth / window.devicePixelRatio),
		height: Math.floor(contentHeight / window.devicePixelRatio),
		left: -frustumWidth / 2,
		right: frustumWidth / 2
	};
	frustrum.aspect = frustrum.height / frustrum.width;
	frustrum.top = frustumWidth * frustrum.aspect / 2;
	frustrum.bottom = -frustumWidth * frustrum.aspect / 2;

	return frustrum;
}

function animateLoop() {
	requestAnimationFrame(animateLoop);
	animate();	
}

function animate() {
	const frustrum = getFrustrum();
	const canvas = renderer.domElement;
	if (canvas.width !== frustrum.width || canvas.height !== frustrum.height) {
		camera.aspect = frustrum.aspect;
		camera.left = frustrum.left;
		camera.right = frustrum.right;
		camera.top = frustrum.top;
		camera.bottom = frustrum.bottom;

		renderer.setSize(frustrum.width, frustrum.height, false);
		camera.updateProjectionMatrix();
	}

    renderer.render(scene, camera);
}

              
            
!
999px

Console