Feature Overview
- Render a 3D layout of grain storage facilities.
- Switch between first-person and third-person camera perspectives.
- Simulate barn door opening and closing animations.
- Animate grain transport vehicles entering and leaving the site.
- Support VR-style viewing modes.
Implementation Details
Displaying Storage Information
Show storage details such as identifier, capacity, and stock date when an object is selected.
StorageController.prototype.displayInfoPanel = function (target, worldPos, onClose) {
const screenPos = Engine3D.convertToScreen(worldPos);
$('#InfoTooltip').remove();
$('body').append(`<div id='InfoTooltip' style='position:absolute;left:${screenPos.x - 30}px;top:${screenPos.y}px;width:2px;height:2px;z-index:1000;'></div>`);
layer.closeAll();
let content = '<div></div>';
let dims = ['150px', '100px'];
if (UIHelper) {
const result = UIHelper.renderStorageCard(1);
if (result) {
content = result.markup;
dims = result.size;
}
}
layer.tips(content, '#InfoTooltip', {
closeBtn: 1,
shade: false,
shadeClose: true,
area: dims,
time: 0,
cancel: function (idx, layero) {
layer.close(idx);
if (onClose) onClose();
},
tips: [2, 'rgba(0,0,0,0.1)']
});
}
Customize the UI content for the storage card:
UIHelper.prototype.renderStorageCard = function (level) {
const randomId = Math.floor(Math.random() * 90);
const randomTons = Math.floor(Math.random() * 10000);
const markup = `
<div style='font-size:32px;color:white;text-align:center;line-height:90px;'>
<small style='font-size:26px'>Silo: 0${randomId}</small><br/>
<small style='font-size:26px'>Capacity: ${randomTons} tons</small><br/>
<small style='font-size:26px'>Date: 2020-05-20</small>
</div>`;
return {
markup: markup,
size: ['auto', '100px']
};
}
Defining a Silo Model
The following JSON defines a silo usinng extruded geometry with specific materials and transformations.
{
"visible": true,
"name": "grainSilo",
"type": "ExtrudeGeometry",
"position": { "x": -7027.718, "y": -2429.816, "z": -2366.768 },
"material": {
"color": 16711680,
"faces": {
"top": { "color": 16777215, "side": 1, "opacity": 1, "texture": "../img/3dImg/dg.jpg", "repeatX": true, "tileX": 0.01, "repeatY": true, "tileY": 0.01 },
"bottom": { "color": 16777215, "side": 1, "opacity": 1, "texture": "../img/3dImg/dg.jpg" },
"side": { "color": 16777215, "opacity": 1, "texture": "../img/3dImg/dg.jpg", "repeatX": true, "tileX": 0.1, "repeatY": true, "tileY": 0.005 }
}
},
"scale": { "x": 1, "y": 1, "z": 1 },
"shape": {
"points": [
{ "x": 0, "y": 0 },
{ "x": 2930, "y": 0 },
{ "x": 2930, "y": 420 },
{ "x": 1530, "y": 550 },
{ "x": 0, "y": 420 }
],
"holes": []
},
"extrude": { "depth": 5900, "segments": 2, "steps": 2, "bevel": false },
"rotation": [
{ "axis": "x", "angle": 0 },
{ "axis": "y", "angle": 1.5707963267948966 },
{ "axis": "z", "angle": 0 }
]
}
Camera Perspectives
Switch to a third-person view:
StorageController.prototype.activateThirdPerson = function () {
Engine3D.moveCamera(
{ x: -13164.34, y: 7433.84, z: -26817.98 },
{ x: 500.68, y: -2274.56, z: -9758.79 },
1000,
() => {
Scene3D.firstPersonMode = false;
Scene3D.mobileView = false;
Scene3D.customControls = false;
}
);
}
Switch to a first-person view with WASD controls:
StorageController.prototype.activateFirstPerson = function () {
layer.msg('Controls: W-S-A-D for movement');
Engine3D.moveCamera(
{ x: 5693.55, y: -2278.79, z: -8633.34 },
{ x: -525.77, y: -1986.19, z: -7655.68 },
1000,
() => {
Scene3D.firstPersonMode = true;
Scene3D.viewHeight = -2000;
Scene3D.mobileView = true;
Scene3D.customControls = true;
}
);
}
Door Animation
Toggle the barn doors open or closed:
StorageController.prototype.toggleDoors = function () {
$('#toggleBtn').hide();
Engine3D.moveCamera(
{ x: -4532.01, y: -2148.20, z: -6284.60 },
{ x: -4012.91, y: -2341.79, z: -5196.68 },
1000,
() => {
const barn = Engine3D.findMesh('mainBarn');
const leftDoor = Engine3D.findMesh('leftDoor');
const rightDoor = Engine3D.findMesh('rightDoor');
const isClosed = $('#toggleBtn').data('status') === 'closed';
if (isClosed) {
new TWEEN.Tween(leftDoor.rotation)
.to({ y: -Math.PI / 1.5 }, 2000).start();
new TWEEN.Tween(rightDoor.rotation)
.to({ y: Math.PI / 1.5 }, 2000)
.onComplete(() => {
$('#toggleBtn').text('Close Doors').show().data('status', 'open');
}).start();
} else {
new TWEEN.Tween(leftDoor.rotation)
.to({ y: 0 }, 2000).start();
new TWEEN.Tween(rightDoor.rotation)
.to({ y: 0 }, 2000)
.onComplete(() => {
$('#toggleBtn').text('Open Doors').show().data('status', 'closed');
}).start();
}
}
);
}
Vehicle Animation
Simulate a truck arriving to load grain:
function animateTruckArrival() {
Engine3D.moveCamera(
{ x: 801.50, y: 958.56, z: -12201.04 },
{ x: -3801.09, y: -2092.37, z: -5062.51 },
1000,
startTruckSequence
);
}
function startTruckSequence() {
const truckBody = Engine3D.findMesh('truckBody');
const trailer = Engine3D.findMesh('trailer');
const grainLoad = Engine3D.findMesh('grainLoad');
grainLoad.position.set(-4189.89, 0, -5816.50);
grainLoad.scale.y = 0.1;
grainLoad.rotation.y = Math.PI / 2;
truckBody.rotation.y = Math.PI / 2;
trailer.rotation.y = Math.PI / 2;
truckBody.visible = true;
trailer.visible = true;
truckBody.position.set(-22422.47, 0, -6239.19);
trailer.position.set(-22620.57, 0, -5932.50);
new TWEEN.Tween(trailer.position)
.to({ x: -3726.21 }, 6000).start();
new TWEEN.Tween(truckBody.position)
.to({ x: -3528.85 }, 6000)
.onComplete(adjustPosition).start();
}
function adjustPosition() {
const truckBody = Engine3D.findMesh('truckBody');
const trailer = Engine3D.findMesh('trailer');
new TWEEN.Tween(truckBody.position).to({ x: -3532.54 }, 2000).start();
new TWEEN.Tween(trailer.position).to({ x: -3729.16 }, 2000)
.onComplete(finalizeLoad).start();
}
Temperature and Humidity Monitoring
Visualize sensor data inside the silo:
StorageController.prototype.showClimateData = function (siloId) {
const sensors = Engine3D.findMeshesByPattern(`sensor_${siloId}_`);
DataService.fetchClimate(siloId, (readings) => {
const labels = Engine3D.createLabels(sensors, readings);
Engine3D.renderLabels(labels);
});
}