2024-09-26 23:53:38
去了个人少的海滩,阳光把沙子晒得烫脚。
大海有个比较好的地方元素比较少,稍微构图就好了。
偶尔也试试正方形构图
适马30mm 1.4 下的人像,非常给力。
2024-09-14 16:19:02
有个机器上的服务可能会炸掉,在修复之前直接重启。懂不懂let it crash.哲学
#!/bin/bash
PORT=端口
SERVICE_NAME=服务名称
if ! nc -z localhost $PORT; then
echo "Port $PORT is not active. Attempting to start $SERVICE_NAME..."
systemctl start $SERVICE_NAME
if systemctl is-active --quiet $SERVICE_NAME; then
echo "$SERVICE_NAME has been started successfully."
else
echo "Failed to start $SERVICE_NAME."
fi
else
echo "Port $PORT is active."
fi
2024-09-13 17:15:47
关键点在于shader的编写
通过按帧号的方式去计算采样的点位
vec4 colorImage = texture(image, vec2(fract(st.s * 50.0 - speed * czm_frameNumber * 50.0 * 0.01), st.t));
被采样的贴图会被绘制到折线上。
import * as Cesium from 'cesium';
/*
流动纹理线
color 颜色
duration 持续时间 毫秒
*/
function PolylineTrailLinkMaterialProperty (color, speed, duration, imageUrl) {
this._definitionChanged = new Cesium.Event()
this._color = undefined
this._colorSubscription = undefined
this.color = color
this.speed = speed
this.duration = duration
this._time = new Date().getTime()
this.imageUrl = imageUrl
}
Object.defineProperties(PolylineTrailLinkMaterialProperty.prototype, {
isConstant: {
get: function () {
return false
}
},
definitionChanged: {
get: function () {
return this._definitionChanged
}
},
color: Cesium.createPropertyDescriptor('color')
})
PolylineTrailLinkMaterialProperty.prototype.getType = function (time) {
return 'PolylineTrailLink'
}
PolylineTrailLinkMaterialProperty.prototype.getValue = function (time, result) {
// debugger
if (!Cesium.defined(result)) {
result = {}
}
result.color = Cesium.Property.getValueOrClonedDefault(
this._color,
time,
Cesium.Color.WHITE,
result.color
)
result.image = this.imageUrl ? this.imageUrl : Cesium.Material.PolylineTrailLinkImage
result.time = ((new Date().getTime() - this._time) % this.duration) / this.duration
result.speed = this.speed
return result
}
PolylineTrailLinkMaterialProperty.prototype.equals = function (other) {
return (
this === other ||
(other instanceof PolylineTrailLinkMaterialProperty &&
Property.equals(this._color, other._color))
)
}
// Cesium.PolylineTrailLinkMaterialProperty = PolylineTrailLinkMaterialProperty
Cesium.Material.PolylineTrailLinkType = 'PolylineTrailLink'
Cesium.Material.PolylineTrailLinkImage = '/fire.png'
Cesium.Material.PolylineTrailLinkSource =
`czm_material czm_getMaterial(czm_materialInput materialInput)
{
czm_material material = czm_getDefaultMaterial(materialInput);
vec2 st = materialInput.st;
vec4 colorImage = texture(image, vec2(fract(st.s * 50.0 - speed * czm_frameNumber * 50.0 * 0.01), st.t));
material.alpha = colorImage.a * color.a;
material.diffuse = (colorImage.rgb+color.rgb)/2.0;
return material;
}`
Cesium.Material._materialCache.addMaterial(
Cesium.Material.PolylineTrailLinkType,
{
fabric: {
type: Cesium.Material.PolylineTrailLinkType,
uniforms: {
color: new Cesium.Color(1.0, 0.0, 0.0, 0.5),
image: Cesium.Material.PolylineTrailLinkImage,
time: 0,
speed: 0.008
},
source: Cesium.Material.PolylineTrailLinkSource
},
translucent: function (material) {
return true
}
}
)
class FlowLine {
constructor(viewer, options) {
this.options = options
this.viewer = viewer
this.id = Cesium.Math.nextRandomNumber()
if (options.id) {
this.id = options.id
}
}
play() {
this.loopMaterial = new PolylineTrailLinkMaterialProperty(
defaults(this.options.color, Cesium.Color.WHITE.withAlpha(0.5)),
defaults(this.options.speed, 0.001),
defaults(this.options.duration, 1750),
defaults(this.options.image, "/guangpu.png")
)
this.line = this.viewer.entities.add({
name: "flowline_" + this.id,
polyline: {
positions: this.options.positions,
width: defaults(this.options.width, 10),
material: this.loopMaterial //修改抛物线材质
}
})
}
destroy() {
if (this.line) {
this.viewer.entities.remove(this.line)
}
}
}
function defaults(value, defaultData) {
return value ? value : defaultData
}
export {
FlowLine
}
2024-09-13 16:41:19
轨迹的来源多种多样,这里直接限定一个格式
Array<{lon: Number, lat: Number, height: Number, timestamp: Number}>
需要提供带海拔高度的坐标,用于获取Catisian3;和当前时间,时间用于提供插值计算。
这里使用Primitive
的方式绘制,用entity绘制可能会有性能问题。点太多了
drawPiont() {
this.points = new Cesium.PointPrimitiveCollection({ blendOption: Cesium.BlendOption.TRANSLUCENT })
this.options.points.map(p => {
return {
position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat, p.height),
color: defaults(this.options.pointColor, Cesium.Color.RED.withAlpha(0.7)),
pixelSize: defaults(this.options.pointWidth, 10)
}
}).forEach(p => this.points.add(p))
this.viewer.scene.primitives.add(this.points)
}
需要打开TRANSLUCENT
到时候显示的效果会好一点。
折线也采用Primitive
的方式绘制。
let degreeHeights = this.options.points.map(p => [p.lon, p.lat, p.height]).flat()
this.polyline = new Cesium.Primitive({
geometryInstances: new Cesium.GeometryInstance({
geometry: new Cesium.PolylineGeometry({
positions: Cesium.Cartesian3.fromDegreesArrayHeights(degreeHeights),
width: defaults(this.options.polylineWidth, 2),
}),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(defaults(this.options.polylineColor, Cesium.Color.YELLOW.withAlpha(0.5))),
},
}),
appearance: new Cesium.PolylineColorAppearance({
translucent: true
}),
})
this.viewer.scene.primitives.add(this.polyline)
直接获取Cartisian3[] 绘制,并预留颜色、宽度等默认参数。
有可能需要绘制流动线。
drawFlowLine() {
this.polyline = new FlowLine(this.viewer, {
// 线 Cartesian3[]
positions: Cesium.Cartesian3.fromDegreesArrayHeights(this.options.points.map(p => [p.lon, p.lat, p.height]).flat()),
// 线宽
width: defaults(this.options.polylineWidth, 2),
// 流动速度
speed: defaults(this.options.flowLineSpeed, 0.01),
// 底色
color: defaults(this.options.flowLineColor, Cesium.Color.TEAL.withAlpha(0.5)),
// 纹理
image: defaults(this.options.flowLineImage, '/arrow-small.png')
})
this.polyline.play()
}
FlowLine
是我自己封装的一个实现,下集再说吧
直接遍历所有的坐标,按顺序两两生成Tween对象
this.group = new TWEEN.Group()
for (var pos = 1; pos < this.options.points.length; pos++) {
let curr = [this.options.points[pos].lon, this.options.points[pos].lat, this.options.points[pos].height]
let last = [this.options.points[lastPos].lon, this.options.points[lastPos].lat, this.options.points[lastPos].height]
let temp = [last[0], last[1], last[2]]
let sepAnimation = new TWEEN.Tween(last)
.repeat(defaults(options?.repeat, 0))
.interpolation(TWEEN.Interpolation.Bezier)
.to(curr, (this.options.points[pos].timestamp - this.options.points[lastPos].timestamp) * options.speed)
.onComplete(()=>{
console.log("on complete--> ", pos);
setTimeout(() => {
this.viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);
}, 1000);
if (callback) {
callback()
}
})
.onUpdate((op) => {})
if (animateChain == null) {
animateChain = sepAnimation
animateChainHead = sepAnimation
} else {
animateChain.chain(sepAnimation)
animateChain.onComplete(() => {
sepAnimation.start()
})
animateChain = sepAnimation
}
this.group.add(sepAnimation)
lastPos = pos
}
function animate() {
that.groupAnimationRequest = requestAnimationFrame(animate);
that.group.update();
}
animate()
首先生成分段动画sepAnimation
, 动画开始的数据为上一次结果,结束为当前坐标位置,两个坐标的时间戳的差为动画的插值计算时长(预留播放倍速因子)。
在Tween
的onUpdate
里写上模型移动的算法,回调里会提供插值的数据。
在onComplete
里写上整个动画链结束后的回调,便于调用方处理后续事务。chain
起来的动画序列只会执行onComplete
一次。
把所有的分段动画都加到group
里,把group
放到requestAnimationFrame
里加入渲染循环。
其中,Tween支持多个动画链接播放,使用chain()即可链接起来
接下来就是显示onUpdate里的逻辑
var direction = Cesium.Cartesian3.subtract(Cesium.Cartesian3.fromDegrees(op[0], op[1], op[2]), Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]), new Cesium.Cartesian3());
Cesium.Cartesian3.normalize(direction, direction);
var rotationMatrix = Cesium.Transforms.rotationMatrixFromPositionVelocity(Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]), direction);
this.model.orientation = Cesium.Quaternion.fromRotationMatrix(rotationMatrix)
先用先后两个点计算出向量direction
。然后计算模型需要偏转+平移
的矩阵rotationMatrix
模型移动了,当参数要求主视角跟随模型的时候,需要锁定主视角到模型
let transform = Cesium.Matrix4.fromRotationTranslation(rotationMatrix, Cesium.Cartesian3.fromDegrees(temp[0], temp[1], temp[2]));
利用上边的rotationMatrix
计算相机的朝向,然后利用lookAtTransform直接锁定过去。
this.viewer.camera.lookAtTransform(transform, new Cesium.HeadingPitchRange(
Cesium.Math.toRadians(defaults(options.trackHeading, 90)),
Cesium.Math.toRadians(defaults(options.trackPitch, -30.0)),
defaults(options.trackRange, 50)
));
这里参数是正后方、距离50米俯视30度。
2024-06-27 18:05:10
b3dm包含一个28B的头部和两个数据table和外部的GLTF文件。
b3dm:
|----header(28 B)--------|--feature table---|--batch table---|---------GLB-----------|
header:
magic | version | byteLength | featureTableJsonByteLength | featureTableBinaryLength | batchTableJsonByteLength | batchTableBinaryLength
feature table:
json | binary
batch table:
json | binary
其中 byteLength = 28 + featureTableJSONByteLength + featureTableBinaryByteLength + batchTableJSONByteLength + batchTableBinaryByteLength + glb的字节长度
magic 是固定的b3dm
featureTableBinaryLength 和 batchTableBinaryLength 在没数据的话,可以为0
feature table 的json 一般直接一个{"BATCH_LENGTH":1}
就行了。
然后是batch table的json,
{"batchId":[0],"maxPoint":[[{},{},{}]],"minPoint":[[{},{},{}]],"name":["default"]}
每个字段都是一个数组,上边的batch length 有多少个,每个字段就有多少个数据。
batchId 指的是 glb 文件里 meshes[].primitives[] 的第几个。链接:STL文件解析及转换到GLTF
maxPoint和minPoint是glb的最大最小顶点。
把文件头、featTableJson、batchTableJson写入文件后,需要使用空格
来padding到8的倍数.
featureTableJsonByteLength = featureTableJsonByteLength + paddingLength
(28 + featureTableJsonByteLength) % 8 == 0
batchTableJsonByteLength = batchTableJsonByteLength + paddingLength
(28 + batchTableJsonByteLength) % 8 == 0
while (buf.size() < 28 + featureJsonLength) {
// add b'20'
for (byte b : " ".getBytes()) {
buf.add(b);
}
}
...
写入头部和批量表等数据后,需要处理GLB的内容来配合头部。
batchTableJson描述了怎么找到GLB文件里primitive的位置。
下一步需要改造glb,在primitive加上_BATCHID
属性,指明accessor,用来读取glb 文件buffer的内容。
"meshes" : [{"primitives" : [ {"attributes" : {"POSITION" : 1,"_BATCHID":2},"indices" : 0} ]}]
这里就会去找到第三个accessor
{"bufferView" : 2,"byteOffset" : 0,"componentType" : 5125,"count" : %d,"type" : "SCALAR","max" : [ 0 ],"min" : [ 0 ], "name":"_batchId"}
然后是他对应的bufferview
{"buffer" : 0,"byteOffset" : %d,"byteLength" : %d,"target" : 34963}
那么写入buffer的是什么数据呢?
int[] _batches = new int[顶点数];
for (int x = 0; x < _batches.length; x++) {
_batches[x] = 0;
}
这些写就好了, 把_batches写道buffer里
最后把这个glb文件写入到b3dm里即可。