在数字地球和地理空间可视化领域,Cesium凭借其强大的3D地球渲染能力,已成为开发者构建沉浸式地理空间应用的首选工具。本文将深入探讨一个创新的Cesium应用实例------倒立四棱锥场景,通过剖析其几何结构、自定义渲染和交互设计,揭示如何将基础几何体转化为富有表现力的3D可视化元素。

一、Cesium与3D可视化:从基础到创新
Cesium是一个开源的JavaScript库,专注于创建高性能、跨平台的3D地球和地图应用。它利用WebGL技术实现硬件加速的3D渲染,支持高精度地形、卫星影像和矢量数据的可视化。本案例中,Cesium不仅展示了其基础功能,更通过自定义几何体和动画效果,探索了3D可视化的新边界。
二、倒立四棱锥的几何结构:解构与重构
传统四棱锥(金字塔)的几何结构通常为底面正方形,顶部尖点。本案例的"倒立四棱锥"则采用创新的几何设计:
javascript
// 定义5个顶点:4个底部顶点 + 1个顶部顶点
var positions = new Float64Array(5 * 3);
// 底部顶点 (z = 0) - 平的底部
positions[0] = 1.0; positions[1] = 1.0; positions[2] = 0.0; // 前右
positions[3] = -1.0; positions[4] = 1.0; positions[5] = 0.0; // 前左
positions[6] = -1.0; positions[7] = -1.0; positions[8] = 0.0; // 后左
positions[9] = 1.0; positions[10] = -1.0; positions[11] = 0.0; // 后右
positions[12] = 0.0; positions[13] = 0.0; positions[14] = 2.0; // 顶部顶点
关键创新在于倒立设计 :通过computeModelMatrix函数中的180度X轴旋转实现:
javascript
// 创建绕X轴旋转180度的矩阵(使四棱锥倒立)
let rotationX = Cesium.Matrix4.fromRotationTranslation(
Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(180))
);
这种几何结构使四棱锥的"尖"朝下,"底"朝上,创造出视觉上独特的倒置效果,为3D场景增添了艺术感和科技感。
三、自定义几何体与着色器:Cesium高级渲染技术
Cesium的高级可视化能力很大程度上依赖于自定义几何体和着色器。本案例中,我们实现了两个关键组件:
1. 面部渲染(Face Rendering)
javascript
function createFaceVertexShader() {
var vertexShader = `
attribute vec3 position;
attribute vec3 normal;
attribute vec2 st;
varying vec3 v_positionEC;
varying vec3 v_normalEC;
varying vec2 v_st;
void main() {
v_positionEC = (czm_modelView * vec4(position, 1.0)).xyz;
v_normalEC = czm_normal * normal;
v_st = st;
gl_Position = czm_modelViewProjection * vec4(position, 1.0);
}
`;
return vertexShader;
}
面部渲染使用Phong光照模型,确保棱角分明:
javascript
material.alpha = color.a;
material.diffuse = color.rgb;
material.specular = 0.5;
material.shininess = 10.0;
gl_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
2. 边线渲染(Edge Rendering)
为增强四棱锥的立体感,添加了黑色边线:
javascript
function createEdgeVertexShader() {
var vertexShader = `
attribute vec3 position;
void main() {
gl_Position = czm_modelViewProjection * vec4(position, 1.0);
}
`;
return vertexShader;
}
边线渲染禁用背面剔除(rawRenderState.cull.enabled = false),确保所有边线可见,并设置线宽为2.0:
javascript
rawRenderState.lineWidth = 2.0;
四、动画效果:动态视觉表达
本案例的动画效果通过startAnimate方法实现,利用了Cesium的帧渲染机制:
javascript
TetrahedronPrimitive.prototype.startAnimate = function () {
let that = this;
this._setInterval = setInterval(animateFunc, 5);
function animateFunc() {
that._angle = that._angle + 0.01 * that._speed;
if (Math.sin(that._angle) < 0) {
that._height = 0.01;
} else {
that._height = -0.01;
}
let translation = new Cesium.Cartesian3(0, 0, that._height);
Cesium.Matrix4.multiplyByTranslation(that._modelMatrix, translation, that._modelMatrix);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(that._speed)));
Cesium.Matrix4.multiply(that._modelMatrix, rotationZ, that._modelMatrix);
}
};
动画包含两个关键运动:
- 垂直摆动 :通过
_height变量实现上下摆动 - 水平旋转:通过Z轴旋转实现缓慢旋转
这种组合运动创造出优雅的"摇摆"效果,使四棱锥在场景中更具生命力。
五、交互式控制:用户友好设计
本案例的UI设计体现了"用户中心"理念,通过精心设计的控制面板实现:
1. 控制面板设计
控制面板采用现代UI设计语言:
- 半透明玻璃效果(
backdrop-filter: blur(10px)) - 精心设计的渐变按钮(
linear-gradient) - 平滑的动画过渡(
transition: all 0.3s ease)
2. 核心控制功能
| 控制项 | 功能描述 | 技术实现 |
|---|---|---|
| 添加/移除四棱锥 | 动态创建和销毁几何体 | viewer.scene.primitives.add/remove |
| 颜色选择 | 实时更改四棱锥颜色 | Cesium.Color.fromCssColorString |
| 动画速度 | 调整动画节奏 | 更新_speed属性并同步到所有四棱锥 |
| 大小调整 | 控制四棱锥尺寸 | 更新_scale属性 |
| 随机位置 | 控制四棱锥生成位置 | 通过useRandomPosition标志切换生成逻辑 |
3. 位置生成算法
javascript
if (this.useRandomPosition) {
const latOffset = (Math.random() - 0.5) * 0.05;
const lonOffset = (Math.random() - 0.5) * 0.05;
const height = Math.random() * 200 + 50;
position = Cesium.Cartesian3.fromDegrees(
116.39 + lonOffset,
39.9 + latOffset,
height
);
} else {
const offset = this.primitives.length * 0.01;
position = Cesium.Cartesian3.fromDegrees(
116.39 + offset,
39.9,
100
);
}
这种位置生成逻辑提供了两种模式:随机位置(更自然的分布)和线性排列(便于观察)。
六、架构设计:模块化与可维护性
本案例采用了清晰的架构设计,确保代码的可维护性和扩展性:
- TetrahedronManager:管理所有四棱锥实例,提供统一的接口
- TetrahedronPrimitive:自定义几何体类,封装渲染和动画逻辑
- 控制面板:与管理器交互,更新状态
这种分层设计使代码结构清晰,便于后续扩展。例如,添加新的几何体类型只需实现新的Primitive类,而无需修改主控制逻辑。
七、代码全景
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cesium 倒立四棱锥场景</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body,
html {
width: 100%;
height: 100%;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #000;
}
#cesiumContainer {
width: 100%;
height: 100%;
}
.cesium-widget-credits,
.cesium-viewer-toolbar {
display: none !important;
}
.control-panel {
position: fixed;
top: 20px;
left: 20px;
background: rgba(25, 25, 35, 0.9);
border-radius: 12px;
padding: 18px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.6);
color: white;
min-width: 280px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
z-index: 1000;
transition: all 0.3s ease;
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 18px;
padding-bottom: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.panel-title {
font-size: 1.3rem;
font-weight: 600;
color: #6aabf2;
}
.panel-toggle {
background: rgba(255, 255, 255, 0.1);
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 6px 10px;
border-radius: 6px;
transition: background 0.3s;
}
.panel-toggle:hover {
background: rgba(255, 255, 255, 0.2);
}
.control-group {
margin-bottom: 18px;
}
.control-label {
display: block;
margin-bottom: 10px;
font-size: 0.95rem;
color: #ccc;
font-weight: 500;
}
.button-group {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 18px;
}
button {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
border: none;
color: white;
padding: 12px 16px;
border-radius: 8px;
cursor: pointer;
font-size: 0.95rem;
font-weight: 500;
transition: all 0.3s ease;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
button:hover {
transform: translateY(-3px);
box-shadow: 0 6px 15px rgba(0, 0, 0, 0.4);
}
button:active {
transform: translateY(0);
}
button:disabled {
background: #555;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.remove-btn {
background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
}
.clear-btn {
background: linear-gradient(135deg, #ff9a00 0%, #ff5e00 100%);
}
.color-picker {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.color-option {
width: 28px;
height: 28px;
border-radius: 50%;
cursor: pointer;
border: 2px solid transparent;
transition: transform 0.2s, border-color 0.2s;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
.color-option:hover {
transform: scale(1.2);
}
.color-option.active {
border-color: white;
transform: scale(1.2);
}
.stats {
position: fixed;
bottom: 20px;
left: 20px;
background: rgba(25, 25, 35, 0.9);
color: white;
padding: 12px 18px;
border-radius: 10px;
font-size: 0.95rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
.slider-container {
display: flex;
align-items: center;
gap: 12px;
}
.slider-value {
min-width: 45px;
text-align: right;
font-weight: 500;
}
input[type="range"] {
flex: 1;
height: 6px;
border-radius: 5px;
background: #444;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #6aabf2;
cursor: pointer;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
.toggle-switch {
position: relative;
display: inline-block;
width: 54px;
height: 28px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #555;
transition: .4s;
border-radius: 28px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 20px;
width: 20px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked+.toggle-slider {
background-color: #6aabf2;
}
input:checked+.toggle-slider:before {
transform: translateX(26px);
}
.panel-collapsed .panel-content {
display: none;
}
.info-box {
position: fixed;
top: 20px;
right: 20px;
background: rgba(25, 25, 35, 0.9);
color: white;
padding: 18px;
border-radius: 12px;
max-width: 320px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.6);
}
.info-title {
font-size: 1.2rem;
margin-bottom: 12px;
color: #6aabf2;
font-weight: 600;
}
.info-box p {
margin-bottom: 8px;
line-height: 1.5;
color: #ddd;
}
.camera-controls {
position: fixed;
bottom: 80px;
left: 20px;
background: rgba(25, 25, 35, 0.9);
color: white;
padding: 12px;
border-radius: 10px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
.camera-controls button {
padding: 8px 12px;
margin: 5px;
font-size: 0.9rem;
}
.coordinates {
position: fixed;
bottom: 20px;
right: 20px;
background: rgba(25, 25, 35, 0.9);
color: white;
padding: 12px 18px;
border-radius: 10px;
font-size: 0.9rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
}
</style>
<script src="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.89/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer"></div>
<div class="control-panel" id="controlPanel">
<div class="panel-header">
<div class="panel-title">倒立四棱锥控制器</div>
<button class="panel-toggle" id="togglePanel">−</button>
</div>
<div class="panel-content">
<div class="button-group">
<button id="addTetrahedron">添加倒立四棱锥</button>
<button id="removeTetrahedron" class="remove-btn">移除四棱锥</button>
</div>
<button id="clearAll" class="clear-btn" style="width: 100%; margin-bottom: 18px;">清除所有</button>
<div class="control-group">
<div class="control-label">四棱锥颜色</div>
<div class="color-picker">
<div class="color-option active" style="background-color: #ff3b30;" data-color="#ff3b30"></div>
<div class="color-option" style="background-color: #4cd964;" data-color="#4cd964"></div>
<div class="color-option" style="background-color: #007aff;" data-color="#007aff"></div>
<div class="color-option" style="background-color: #ffcc00;" data-color="#ffcc00"></div>
<div class="color-option" style="background-color: #ff9500;" data-color="#ff9500"></div>
<div class="color-option" style="background-color: #8e8e93;" data-color="#8e8e93"></div>
</div>
</div>
<div class="control-group">
<div class="control-label">动画速度</div>
<div class="slider-container">
<input type="range" id="speedSlider" min="0.1" max="2" step="0.1" value="1">
<span class="slider-value" id="speedValue">1.0</span>
</div>
</div>
<div class="control-group">
<div class="control-label">四棱锥大小</div>
<div class="slider-container">
<input type="range" id="sizeSlider" min="5" max="30" step="1" value="15">
<span class="slider-value" id="sizeValue">15</span>
</div>
</div>
<div class="control-group">
<div class="control-label">随机位置</div>
<label class="toggle-switch">
<input type="checkbox" id="randomPosition" checked>
<span class="toggle-slider"></span>
</label>
</div>
</div>
</div>
<div class="stats" id="stats">
倒立四棱锥数量: <span id="tetraCount">0</span>
</div>
<div class="camera-controls">
<button id="resetView">重置视角</button>
<button id="viewAll">查看全部</button>
</div>
<div class="coordinates" id="coordinates">
经度: --<br>纬度: --<br>高度: --
</div>
<div class="info-box">
<div class="info-title">操作说明</div>
<p>• 点击"添加倒立四棱锥"按钮添加新的倒立四棱锥</p>
<p>• 点击"移除四棱锥"按钮移除最后一个四棱锥</p>
<p>• 使用颜色选择器更改四棱锥颜色</p>
<p>• 调整动画速度和四棱锥大小</p>
<p>• 启用/禁用随机位置生成</p>
</div>
<script>
// 使用您的Cesium Ion令牌
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzZDg3N2ZiZC05MzBiLTRmZmYtYTU1Yy1kOGNiZWU4ZDU4Y2IiLCJpZCI6MTQ0MDksInNjb3BlcyI6WyJhc2wiLCJhc3IiLCJhc3ciLCJnYyJdLCJpYXQiOjE1NjcyNTQ1NDh9.qCtL6_oYhN3VErce9lAiIZCXevo3kPOE4YKD7IDuGiY'
// 初始化Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
shouldAnimate: true,
infoBox: false,
animation: false,
timeline: false,
fullscreenButton: false,
terrainProvider: Cesium.createWorldTerrain({
requestWaterMask: true,
requestVertexNormals: true
}),
selectionIndicator: false,
});
// 隐藏版权信息
viewer._cesiumWidget._creditContainer.style.display = "none";
// 设置初始相机位置
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 1000),
orientation: {
heading: 0,
pitch: -0.5,
roll: 0
}
});
// 四棱锥管理
class TetrahedronManager {
constructor() {
this.primitives = [];
this.selectedColor = Cesium.Color.fromCssColorString('#ff3b30');
this.animationSpeed = 1.0;
this.size = 15;
this.useRandomPosition = true;
// 使用经纬度坐标
this.basePosition = Cesium.Cartesian3.fromDegrees(116.39, 39.9, 0);
}
addTetrahedron() {
// 计算位置
let position;
if (this.useRandomPosition) {
// 在基础位置周围随机生成位置
const latOffset = (Math.random() - 0.5) * 0.05;
const lonOffset = (Math.random() - 0.5) * 0.05;
const height = Math.random() * 200 + 50;
position = Cesium.Cartesian3.fromDegrees(
116.39 + lonOffset,
39.9 + latOffset,
height
);
} else {
// 线性排列
const offset = this.primitives.length * 0.01;
position = Cesium.Cartesian3.fromDegrees(
116.39 + offset,
39.9,
100
);
}
// 创建四棱锥
const primitive = new TetrahedronPrimitive({
position: position,
color: this.selectedColor,
scale: new Cesium.Cartesian3(this.size, this.size, this.size * 1.5),
speed: this.animationSpeed
});
// 添加到场景
viewer.scene.primitives.add(primitive);
primitive.startAnimate();
// 保存引用
this.primitives.push(primitive);
// 更新统计信息
this.updateStats();
// 如果是第一个四棱锥,飞行到它
if (this.primitives.length === 1) {
viewer.flyTo(primitive);
}
return primitive;
}
removeTetrahedron() {
if (this.primitives.length === 0) return;
// 获取最后一个四棱锥
const primitive = this.primitives.pop();
// 停止动画
primitive.closeAnimate();
// 从场景中移除
viewer.scene.primitives.remove(primitive);
// 更新统计信息
this.updateStats();
}
clearAll() {
while (this.primitives.length > 0) {
this.removeTetrahedron();
}
}
updateStats() {
document.getElementById('tetraCount').textContent = this.primitives.length;
}
setColor(color) {
this.selectedColor = color;
}
setAnimationSpeed(speed) {
this.animationSpeed = speed;
// 更新所有四棱锥的速度
this.primitives.forEach(primitive => {
primitive.setSpeed(speed);
});
}
setSize(size) {
this.size = size;
}
setRandomPosition(enabled) {
this.useRandomPosition = enabled;
}
viewAll() {
if (this.primitives.length === 0) return;
// 计算所有四棱锥的边界
const positions = this.primitives.map(p => p._localPosition);
const boundingSphere = Cesium.BoundingSphere.fromPoints(positions);
// 飞行到边界球
viewer.camera.viewBoundingSphere(boundingSphere,
new Cesium.HeadingPitchRange(0, -0.5, boundingSphere.radius * 3));
}
}
// 创建管理器实例
const tetraManager = new TetrahedronManager();
// 设置UI事件监听
document.getElementById('addTetrahedron').addEventListener('click', () => {
tetraManager.addTetrahedron();
});
document.getElementById('removeTetrahedron').addEventListener('click', () => {
tetraManager.removeTetrahedron();
});
document.getElementById('clearAll').addEventListener('click', () => {
tetraManager.clearAll();
});
// 颜色选择
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', function () {
// 移除所有active类
document.querySelectorAll('.color-option').forEach(el => {
el.classList.remove('active');
});
// 添加active类到当前选项
this.classList.add('active');
// 设置颜色
const color = Cesium.Color.fromCssColorString(this.dataset.color);
tetraManager.setColor(color);
});
});
// 动画速度滑块
const speedSlider = document.getElementById('speedSlider');
const speedValue = document.getElementById('speedValue');
speedSlider.addEventListener('input', function () {
const value = parseFloat(this.value);
speedValue.textContent = value.toFixed(1);
tetraManager.setAnimationSpeed(value);
});
// 大小滑块
const sizeSlider = document.getElementById('sizeSlider');
const sizeValue = document.getElementById('sizeValue');
sizeSlider.addEventListener('input', function () {
const value = parseInt(this.value);
sizeValue.textContent = value;
tetraManager.setSize(value);
});
// 随机位置开关
const randomPosition = document.getElementById('randomPosition');
randomPosition.addEventListener('change', function () {
tetraManager.setRandomPosition(this.checked);
});
// 面板折叠/展开
document.getElementById('togglePanel').addEventListener('click', function () {
const panel = document.getElementById('controlPanel');
panel.classList.toggle('panel-collapsed');
this.textContent = panel.classList.contains('panel-collapsed') ? '+' : '−';
});
// 相机控制
document.getElementById('resetView').addEventListener('click', function () {
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.39, 39.9, 1000),
orientation: {
heading: 0,
pitch: -0.5,
roll: 0
}
});
});
document.getElementById('viewAll').addEventListener('click', function () {
tetraManager.viewAll();
});
// 更新坐标显示
function updateCoordinates() {
const cartesian = viewer.camera.position;
const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
const longitude = Cesium.Math.toDegrees(cartographic.longitude).toFixed(5);
const latitude = Cesium.Math.toDegrees(cartographic.latitude).toFixed(5);
const height = cartographic.height.toFixed(2);
document.getElementById('coordinates').innerHTML =
`经度: ${longitude}<br>纬度: ${latitude}<br>高度: ${height}`;
}
viewer.scene.postRender.addEventListener(updateCoordinates);
// 添加一些初始四棱锥
setTimeout(() => {
tetraManager.addTetrahedron();
tetraManager.addTetrahedron();
tetraManager.addTetrahedron();
}, 1000);
// 重新设计四棱锥几何结构,添加边线
function TetrahedronPrimitive(options) {
this.show = true;
this._faceCommand = undefined;
this._edgeCommand = undefined;
this._enuMatrix = undefined;
this._scaleMatrix = undefined;
this._localPosition = options.position;
this._createFaceCommand = createFaceCommand;
this._createEdgeCommand = createEdgeCommand;
this._angle = 0;
this._distance = Cesium.defaultValue(options.distance, 1);
this._setInterval = undefined;
this._viewer = viewer;
this._speed = Cesium.defaultValue(options.speed, 1.0);
this._color = Cesium.defaultValue(options.color, new Cesium.Color(1.0, 0.0, 0.0, 1.0));
this._edgeColor = new Cesium.Color(0.0, 0.0, 0.0, 1.0); // 黑色边线
this._scale = Cesium.defaultValue(options.scale, new Cesium.Cartesian3(10, 10, 15));
this._texture = undefined;
// 计算模型矩阵
this._modelMatrix = computeModelMatrix(this);
this._height = computeHeight(this);
}
TetrahedronPrimitive.prototype.update = function (frameState) {
if (!this.show) {
return;
}
// 创建面命令
if (!Cesium.defined(this._faceCommand)) {
this._faceCommand = this._createFaceCommand(frameState.context, this);
this._faceCommand.pickId = 'v_pickColor';
}
// 创建边线命令
if (!Cesium.defined(this._edgeCommand)) {
this._edgeCommand = this._createEdgeCommand(frameState.context, this);
this._edgeCommand.pickId = 'v_pickColor';
}
// 添加到渲染列表
if (Cesium.defined(this._faceCommand)) {
frameState.commandList.push(this._faceCommand);
}
if (Cesium.defined(this._edgeCommand)) {
frameState.commandList.push(this._edgeCommand);
}
};
TetrahedronPrimitive.prototype.isDestroyed = function () {
return false;
};
TetrahedronPrimitive.prototype.destroy = function () {
if (Cesium.defined(this._faceCommand)) {
this._faceCommand.shaderProgram = this._faceCommand.shaderProgram && this._faceCommand.shaderProgram.destroy();
}
if (Cesium.defined(this._edgeCommand)) {
this._edgeCommand.shaderProgram = this._edgeCommand.shaderProgram && this._edgeCommand.shaderProgram.destroy();
}
return Cesium.destroyObject(this);
};
// 开启动画
TetrahedronPrimitive.prototype.startAnimate = function () {
let that = this;
this._setInterval = setInterval(animateFunc, 5);
function animateFunc() {
that._angle = that._angle + 0.01 * that._speed;
if (Math.sin(that._angle) < 0) {
that._height = 0.01;
} else {
that._height = -0.01;
}
let translation = new Cesium.Cartesian3(0, 0, that._height);
Cesium.Matrix4.multiplyByTranslation(that._modelMatrix, translation, that._modelMatrix);
let rotationZ = Cesium.Matrix4.fromRotationTranslation(Cesium.Matrix3.fromRotationZ(Cesium.Math.toRadians(that._speed)));
Cesium.Matrix4.multiply(that._modelMatrix, rotationZ, that._modelMatrix);
}
};
// 关闭动画
TetrahedronPrimitive.prototype.closeAnimate = function () {
clearInterval(this._setInterval);
};
// 设置动画速度
TetrahedronPrimitive.prototype.setSpeed = function (speed) {
this._speed = speed;
};
// 创建面的绘制命令
function createFaceCommand(context, tetrahedronPrimitive) {
var translucent = false;
var closed = true;
var vs = createFaceVertexShader();
var fs = createFaceFragmentShader();
var rawRenderState = Cesium.Appearance.getDefaultRenderState(translucent, closed, undefined);
// 启用背面剔除,确保棱角分明
rawRenderState.cull = {
enabled: true
};
var renderState = Cesium.RenderState.fromCache(rawRenderState);
var vertexShaderSource = new Cesium.ShaderSource({
sources: [vs]
});
var fragmentShaderSource = new Cesium.ShaderSource({
sources: [fs]
});
var uniformMap = {
color: function () {
return tetrahedronPrimitive._color;
}
};
let attributeLocations = {
position: 0,
textureCoordinates: 1
};
var shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShaderSource,
fragmentShaderSource: fragmentShaderSource,
attributeLocations: attributeLocations
});
return new Cesium.DrawCommand({
vertexArray: createFaceVertexArray(context),
primitiveType: Cesium.PrimitiveType.TRIANGLES,
renderState: renderState,
shaderProgram: shaderProgram,
uniformMap: uniformMap,
owner: this,
pass: Cesium.Pass.TRANSLUCENT,
modelMatrix: tetrahedronPrimitive._modelMatrix,
});
}
// 创建边线的绘制命令
function createEdgeCommand(context, tetrahedronPrimitive) {
var translucent = false;
var closed = true;
var vs = createEdgeVertexShader();
var fs = createEdgeFragmentShader();
var rawRenderState = Cesium.Appearance.getDefaultRenderState(translucent, closed, undefined);
// 禁用背面剔除,确保边线可见
rawRenderState.cull = {
enabled: false
};
// 设置线宽
rawRenderState.lineWidth = 2.0;
var renderState = Cesium.RenderState.fromCache(rawRenderState);
var vertexShaderSource = new Cesium.ShaderSource({
sources: [vs]
});
var fragmentShaderSource = new Cesium.ShaderSource({
sources: [fs]
});
var uniformMap = {
color: function () {
return tetrahedronPrimitive._edgeColor;
}
};
let attributeLocations = {
position: 0
};
var shaderProgram = Cesium.ShaderProgram.fromCache({
context: context,
vertexShaderSource: vertexShaderSource,
fragmentShaderSource: fragmentShaderSource,
attributeLocations: attributeLocations
});
return new Cesium.DrawCommand({
vertexArray: createEdgeVertexArray(context),
primitiveType: Cesium.PrimitiveType.LINES,
renderState: renderState,
shaderProgram: shaderProgram,
uniformMap: uniformMap,
owner: this,
pass: Cesium.Pass.TRANSLUCENT,
modelMatrix: tetrahedronPrimitive._modelMatrix,
});
}
function createFaceVertexArray(context) {
let attributeLocations = {
position: 0,
textureCoordinates: 1
};
var positionsAndIndice = createFacePositionsAndIndice();
var geometry = new Cesium.Geometry({
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3,
values: positionsAndIndice.positions
}),
textureCoordinates: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2,
values: positionsAndIndice.sts
}),
},
indices: positionsAndIndice.indices,
primitiveType: Cesium.PrimitiveType.TRIANGLES,
boundingSphere: Cesium.BoundingSphere.fromVertices(positionsAndIndice.positions)
});
var geometryNormal = Cesium.GeometryPipeline.computeNormal(geometry);
var vertexArray = Cesium.VertexArray.fromGeometry({
context: context,
geometry: geometryNormal,
attributeLocations: attributeLocations,
bufferUsage: Cesium.BufferUsage.STATIC_DRAW,
});
return vertexArray;
}
function createEdgeVertexArray(context) {
let attributeLocations = {
position: 0
};
var positionsAndIndice = createEdgePositionsAndIndice();
var geometry = new Cesium.Geometry({
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3,
values: positionsAndIndice.positions
})
},
indices: positionsAndIndice.indices,
primitiveType: Cesium.PrimitiveType.LINES,
boundingSphere: Cesium.BoundingSphere.fromVertices(positionsAndIndice.positions)
});
var vertexArray = Cesium.VertexArray.fromGeometry({
context: context,
geometry: geometry,
attributeLocations: attributeLocations,
bufferUsage: Cesium.BufferUsage.STATIC_DRAW,
});
return vertexArray;
}
// 创建面的顶点和索引
function createFacePositionsAndIndice() {
// 定义5个顶点:4个底部顶点 + 1个顶部顶点
var positions = new Float64Array(5 * 3);
// 底部顶点 (z = 0) - 平的底部
// 顶点0: 前右
positions[0] = 1.0;
positions[1] = 1.0;
positions[2] = 0.0;
// 顶点1: 前左
positions[3] = -1.0;
positions[4] = 1.0;
positions[5] = 0.0;
// 顶点2: 后左
positions[6] = -1.0;
positions[7] = -1.0;
positions[8] = 0.0;
// 顶点3: 后右
positions[9] = 1.0;
positions[10] = -1.0;
positions[11] = 0.0;
// 顶点4: 顶部顶点 (尖朝上)
positions[12] = 0.0;
positions[13] = 0.0;
positions[14] = 2.0;
// 定义三角形索引 - 确保所有面都定义
// 6个三角形:4个侧面 + 2个底部三角形
var indices = new Uint16Array(6 * 3);
// 侧面1: 前面 (顶点0, 1, 4)
indices[0] = 0;
indices[1] = 1;
indices[2] = 4;
// 侧面2: 左面 (顶点1, 2, 4)
indices[3] = 1;
indices[4] = 2;
indices[5] = 4;
// 侧面3: 后面 (顶点2, 3, 4)
indices[6] = 2;
indices[7] = 3;
indices[8] = 4;
// 侧面4: 右面 (顶点3, 0, 4)
indices[9] = 3;
indices[10] = 0;
indices[11] = 4;
// 底部1: 三角形1 (顶点0, 3, 2)
indices[12] = 0;
indices[13] = 3;
indices[14] = 2;
// 底部2: 三角形2 (顶点0, 2, 1)
indices[15] = 0;
indices[16] = 2;
indices[17] = 1;
// 纹理坐标
var sts = new Float32Array([
// 前面
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
// 左面
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
// 后面
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
// 右面
0.0, 0.0,
1.0, 0.0,
0.5, 1.0,
// 底部1
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
// 底部2
0.0, 0.0,
1.0, 1.0,
0.0, 1.0
]);
return {
indices: indices,
positions: positions,
sts: sts
};
}
// 创建边线的顶点和索引
function createEdgePositionsAndIndice() {
// 使用与面相同的顶点位置
var positions = new Float64Array(5 * 3);
// 底部顶点 (z = 0) - 平的底部
// 顶点0: 前右
positions[0] = 1.0;
positions[1] = 1.0;
positions[2] = 0.0;
// 顶点1: 前左
positions[3] = -1.0;
positions[4] = 1.0;
positions[5] = 0.0;
// 顶点2: 后左
positions[6] = -1.0;
positions[7] = -1.0;
positions[8] = 0.0;
// 顶点3: 后右
positions[9] = 1.0;
positions[10] = -1.0;
positions[11] = 0.0;
// 顶点4: 顶部顶点 (尖朝上)
positions[12] = 0.0;
positions[13] = 0.0;
positions[14] = 2.0;
// 定义边线索引 - 8条边
var indices = new Uint16Array(8 * 2);
// 底部边线
indices[0] = 0;
indices[1] = 1;
indices[2] = 1;
indices[3] = 2;
indices[4] = 2;
indices[5] = 3;
indices[6] = 3;
indices[7] = 0;
// 侧边线
indices[8] = 0;
indices[9] = 4;
indices[10] = 1;
indices[11] = 4;
indices[12] = 2;
indices[13] = 4;
indices[14] = 3;
indices[15] = 4;
return {
indices: indices,
positions: positions
};
}
function createFaceVertexShader() {
var vertexShader =
`
attribute vec3 position;
attribute vec3 normal;
attribute vec2 st;
attribute float batchId;
varying vec3 v_positionEC;
varying vec3 v_normalEC;
varying vec2 v_st;
varying vec4 v_pickColor;
void main()
{
v_positionEC = (czm_modelView * vec4(position, 1.0)).xyz; // position in eye coordinates
v_normalEC = czm_normal * normal; // normal in eye coordinates
v_st = st;
//v_pickColor = czm_batchTable_pickColor(batchId);
gl_Position = czm_modelViewProjection * vec4(position, 1.0);
}
`;
return vertexShader;
}
function createFaceFragmentShader() {
var fragmentShader =
`
varying vec3 v_positionEC;
varying vec3 v_normalEC;
varying vec2 v_st;
uniform vec4 color;
varying vec4 v_pickColor;
void main()
{
vec3 positionToEyeEC = -v_positionEC;
vec3 normalEC = normalize(v_normalEC);
// 使用正确的法线计算光照,确保棱角分明
czm_materialInput materialInput;
materialInput.normalEC = normalEC;
materialInput.positionToEyeEC = positionToEyeEC;
materialInput.st = v_st;
czm_material material = czm_getDefaultMaterial(materialInput);
// 使用Phong光照模型,确保棱角分明
material.alpha = color.a;
material.diffuse = color.rgb;
material.specular = 0.5;
material.shininess = 10.0;
gl_FragColor = czm_phong(normalize(positionToEyeEC), material, czm_lightDirectionEC);
}
`;
return fragmentShader;
}
function createEdgeVertexShader() {
var vertexShader =
`
attribute vec3 position;
void main()
{
gl_Position = czm_modelViewProjection * vec4(position, 1.0);
}
`;
return vertexShader;
}
function createEdgeFragmentShader() {
var fragmentShader =
`
uniform vec4 color;
void main()
{
gl_FragColor = color;
}
`;
return fragmentShader;
}
function computeModelMatrix(tetrahedronPrimitive) {
let enuMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(tetrahedronPrimitive._localPosition);
let scaleMatrix = Cesium.Matrix4.fromScale(tetrahedronPrimitive._scale);
// 创建绕X轴旋转180度的矩阵(使四棱锥倒立)
let rotationX = Cesium.Matrix4.fromRotationTranslation(
Cesium.Matrix3.fromRotationX(Cesium.Math.toRadians(180))
);
// 先缩放,再旋转,最后定位
let modelMatrix = Cesium.Matrix4.multiply(scaleMatrix, rotationX, new Cesium.Matrix4());
modelMatrix = Cesium.Matrix4.multiply(enuMatrix, modelMatrix, new Cesium.Matrix4());
tetrahedronPrimitive._scaleMatrix = scaleMatrix;
tetrahedronPrimitive._enuMatrix = enuMatrix;
return modelMatrix;
}
function computeHeight(tetrahedronPrimitive) {
let point = Cesium.Cartesian3.fromElements(0, 0, tetrahedronPrimitive._distance, new Cesium.Cartesian3());
let enuPoint = Cesium.Matrix4.multiplyByPoint(tetrahedronPrimitive._enuMatrix, point, new Cesium.Cartesian3());
let upPositionEC = Cesium.Matrix4.multiplyByPoint(tetrahedronPrimitive._viewer.scene.camera._viewMatrix, enuPoint, new Cesium.Cartesian3());
let upPositionPC = Cesium.Matrix4.multiplyByPoint(tetrahedronPrimitive._viewer.scene.camera.frustum.projectionMatrix, upPositionEC, new Cesium.Cartesian3());
return Cesium.Cartesian3.normalize(upPositionPC, new Cesium.Cartesian3()).z;
}
</script>
</body>
</html>