wtcsim.com
Model zřícení WTC, který pochopí (téměř) každý | Simplified WTC Floor Collapse Model

//wtcsim.com | Simplified WTC Floor Collapse Simulator
//JS numeric model version 1.0.0
//(C) 2024 Ing. Jan Kolar

//general variables:
var simTimer, timeSeconds, dt, simInterval, pause, floorCounter;

//building characteristics:
var floorThickness;
var floorDamageEnergy;

//falling block of debris:
var weight, bottomSideHeight, velocity, startFloor;

//falling upper part:
var upperWeight, upperTopSideHeight, upperVelocity, upperMaterialLoss, upperAcceleration, initialWeight, upperCoreWeight;

//constants:
const g = 9.81;
const floorHeight = 3.791;

//HTML elements:
var velocityIndicator = document.getElementById("velocity");
var timeIndicator = document.getElementById("time");
var floorIndicator = document.getElementById("floors");
var startFloorEntry = document.getElementById("startFloor");
var initialWeightEntry = document.getElementById("initialWeight");
var upperCoreWeightEntry = document.getElementById("upperCoreWeight");
var floorDamageEnergyEntry = document.getElementById("floorDamageEnergy");
var floorThicknessEntry = document.getElementById("floorThickness");
var upperMaterialLossPctEntry = document.getElementById("upperMaterialLoss");
var upperAccelerationEntry = document.getElementById("upperAcceleration");

//SVG elements
var upperPartRect = document.getElementById("upperPart");
var debrisBlockRect = document.getElementById("debrisBlock");
var remainingFloorsRect = document.getElementById("remainingFloors");
var pUpperFloors = document.getElementById("pUpperFloors");

//*****************************************************************
// Numeric model
//*****************************************************************

function onTimer() {
	if (pause == 1) return;
	var ad, E, dh, dw, fp;

	//*** time counting ***
 	
	timeSeconds = timeSeconds + dt;  // dt = 0.02 s
	
	//*** upper part ***
	
	//something still remains in the upper part?
	if (upperWeight > 0) {
		//upper part fall velocity
		upperVelocity = upperVelocity + upperAcceleration * dt;
		//upper part fall added distance
		ad = upperVelocity * dt;
		//move the upper part by the added distance
		upperTopSideHeight = upperTopSideHeight - ad;
		//upper part portion falling into the block of debris
		dh = topSideHeight() - upperBottomSideHeight();
		if (dh > 0) {
			//the portion weight
			dw = dh / floorHeight;	
			floorCounter = floorCounter + dw;
			//subtract from the upper part weight
			upperWeight = upperWeight - dw;
			if (upperWeight < 0) upperWeight = 0;
			//add the core weight portion
			dw = dw + dh * upperCoreWeight / floorHeight;
			//add the portion into the block of debris
			addToFallingDebrisBlock(dw*(1-upperMaterialLoss), upperVelocity);
		}
	}
	
	//*** block of debris ***
	
	//fall velocity
	velocity = velocity + g * dt;			
	if (weight < initialWeight) velocity = 0;
	//fall added distance
	ad = velocity * dt;
	if (bottomSideHeight <= 0) ad = 0; //debris reached ground
	//move the collapse front by the added distance
	bottomSideHeight = bottomSideHeight - ad;
	
	//kinetic energy
	E = (weight * velocity * velocity) / 2;		// E = 1/2 * mv^2
	//damaged floor portion
	fp = ad / floorHeight;
	//subtract energy required for the floor damage
	E = E - floorDamageEnergy * fp;
	if (E < 0) {
		velocity = 0;
	} else {
		//new velocity after the floor portion damage
		velocity = Math.sqrt(2 * E / weight);	// v = sqrt(2 * E / m)
		//add the damaged floor portion into the block of debris
		addToFallingDebrisBlock(fp, 0);
		floorCounter = floorCounter + fp;
	}
		
	//display results	
	updateGUI();	
	
	//simulation ends?
	if (((bottomSideHeight <= 0) || (velocity <= 0)) && (upperWeight == 0)) {
		stopSimulation();
	}
}

//*****************************************************************

//Misc.

function addToFallingDebrisBlock(addedWeight, itsVelocity) {
	var p1 = weight * velocity;
	var p2 = addedWeight * itsVelocity;
	weight = weight + addedWeight;		// m = m1 + m2
	velocity = (p1 + p2) / weight;		// v = (p1+p2)/(m1+m2)
}

function topSideHeight() {
	return bottomSideHeight + weight * floorThickness;
}

function upperBottomSideHeight() {
	return upperTopSideHeight - upperWeight * floorHeight;
}

//GUI Misc.

function presetSelect() {
	var p = document.getElementById("preset");
	var v = p.value;
	
	if ( v==1 ) {
		startFloorEntry.value = 94;
		initialWeightEntry.value = 2;
		floorDamageEnergyEntry.value = 3.0;
		floorThicknessEntry.value = 0.2;
		upperAccelerationEntry.value = 7;
		upperMaterialLossPctEntry.value = 0;
	}
	if ( v==2 ) {
		startFloorEntry.value = 78;
		initialWeightEntry.value = 1;
		floorDamageEnergyEntry.value = 3.0;
		floorThicknessEntry.value = 0.2;
		upperAccelerationEntry.value = 7;
		upperMaterialLossPctEntry.value = 10;
	}
}

function updateGUI() {
	//text output
	updateIndicators();
	
	//graphic output
	var y, h;	
	debrisBlockRect.setAttribute("y", 417 - topSideHeight());
	debrisBlockRect.setAttribute("height", weight * floorThickness);
	if (bottomSideHeight < 0) bottomSideHeight = 0;
	remainingFloorsRect.setAttribute("y", 417 - bottomSideHeight);
	remainingFloorsRect.setAttribute("height", bottomSideHeight);
	y = 417 - upperTopSideHeight;
	upperPartRect.setAttribute("y", y);
	h = upperWeight * floorHeight;
	if (h < 0) h = 0;
	upperPartRect.setAttribute("height", h);
	pUpperFloors.patternTransform.baseVal.getItem(0).setTranslate(0,y);
}

function updateIndicators() {
	floorIndicator.innerHTML = Math.round(floorCounter);
	velocityIndicator.innerHTML = velocity.toFixed(1)+" m/s";
	timeIndicator.innerHTML = timeSeconds.toFixed(2)+" s";
}


//GUI Buttons

function startSimulation() {
	resetSimulation();
	if (velocity > 0) return;
	simInterval = 20;
	dt = simInterval / 1000;
	simTimer = setInterval(onTimer, simInterval);
}

function stopSimulation() {
		velocity = 0;
		clearInterval(simTimer);
		pause = 0;
}

function pauseSimulation() {
	pause = 1;
}

function resetSimulation() {
	pause = 0;
	if (velocity > 0) return;
	
	startFloor = parseInt(startFloorEntry.value);
	floorThickness = parseFloat(floorThicknessEntry.value);
	initialWeight = parseInt(initialWeightEntry.value);
	floorDamageEnergy = parseFloat(floorDamageEnergyEntry.value) * g * floorHeight;
	upperAcceleration = parseFloat(upperAccelerationEntry.value);
	upperMaterialLoss = parseInt(upperMaterialLossPctEntry.value) / 100;
	upperCoreWeight = parseFloat(upperCoreWeightEntry.value);
	
	bottomSideHeight = floorHeight * startFloor;
	upperWeight = 110 - startFloor ;
	upperTopSideHeight = 417;

	weight = 0;
	timeSeconds = 0;
	floorHeightCounter = 0;
	floorCounter = 0;
	velocity = 0;
	upperVelocity = 0;

	updateIndicators();

	debrisBlockRect.setAttribute("height", 0);
	remainingFloorsRect.setAttribute("height", 417);
	remainingFloorsRect.setAttribute("y", 0);
	upperPartRect.setAttribute("height", upperWeight * floorHeight);
	upperPartRect.setAttribute("y", 0);
	pUpperFloors.patternTransform.baseVal.getItem(0).setTranslate(0,0);
}

resetSimulation();