SoulApp's planet looks so cool! ! !
Without saying anything, the first picture:
Basic knowledge
First we have to come up with our mathematical knowledge:
- The conversion relationship between the spherical coordinate system (r, θ, φ) and the rectangular coordinate system (x, y, z):
x = rsinθcosφ.
y = rsinθsinφ.
z = rcosθ.
Q: What! Why the whole spherical coordinate system?
A: Because of the planet, the position information is of course a simpler coordinate system.
Implementation ideas
The core algorithm comes from: 3dTagCloudAndroid
has made some modified effects on this basis
1. Obtain uniformly distributed point coordinates:
for (int i = 1; i < count + 1; i++) {
// 平均(三维直角得Z轴等分[-1,1]) θ范围[-π/2,π/2])
phi = Math.acos(-1.0 + (2.0 * i - 1.0) / count);
theta = Math.sqrt(count * Math.PI) * phi;
}
2. Three-dimensional coordinates
planetModelCloud.get(i - 1).setLocX((float) (radius * Math.cos(theta) * Math.sin(phi)));
planetModelCloud.get(i - 1).setLocY((float) (radius * Math.sin(theta) * Math.sin(phi)));
planetModelCloud.get(i - 1).setLocZ((float) (radius * Math.cos(phi)));
3. Coordinate rotation
The rotation transformation in three-dimensional space is more complicated than the rotation transformation in two-dimensional space. In addition to the rotation angle, the rotation axis must be specified.
If the three coordinate axes x, y, and z of the coordinate system are respectively used as the rotation axes, the point actually only rotates two-dimensionally on the plane perpendicular to the coordinate axis. At this time, using the two-dimensional rotation formula can directly derive the three-dimensional rotation transformation matrix.
It is stipulated that in the right-handed coordinate system, the positive direction of the object rotation is the right-handed spiral direction, that is, the counterclockwise direction is viewed from the origin of the positive half axis of the axis.
Around X axis
Around Y axis
Around Z axis
/**
* 返回角度转换成弧度之后各方向的值
* <p>
* 1度=π/180
*
* @param mAngleX x方向旋转距离
* @param mAngleY y方向旋转距离
* @param mAngleZ z方向旋转距离
*/
private void sineCosine(float mAngleX, float mAngleY, float mAngleZ) {
double degToRad = (Math.PI / 180);
sinAngleX = (float) Math.sin(mAngleX * degToRad);
cosAngleX = (float) Math.cos(mAngleX * degToRad);
sinAngleY = (float) Math.sin(mAngleY * degToRad);
cosAngleY = (float) Math.cos(mAngleY * degToRad);
sinAngleZ = (float) Math.sin(mAngleZ * degToRad);
cosAngleZ = (float) Math.cos(mAngleZ * degToRad);
}
Use the above matrix to calculate the rotated three-dimensional rectangular coordinates:
// 绕x轴旋转
float rx1 = (planetModel.getLocX());
float ry1 = (planetModel.getLocY()) * cosAngleX + planetModel.getLocZ() * -sinAngleX;
float rz1 = (planetModel.getLocY()) * sinAngleX + planetModel.getLocZ() * cosAngleX;
// 绕y轴旋转
float rx2 = rx1 * cosAngleY + rz1 * sinAngleY;
float ry2 = ry1;
float rz2 = rx1 * -sinAngleY + rz1 * cosAngleY;
// 绕z轴旋转
float rx3 = rx2 * cosAngleZ + ry2 * -sinAngleZ;
float ry3 = rx2 * sinAngleZ + ry2 * cosAngleZ;
float rz3 = rz2;
// 将数组设置为新位置
planetModel.setLocX(rx3);
planetModel.setLocY(ry3);
planetModel.setLocZ(rz3);
4. Calculate the two-dimensional coordinates (in order to make the two-dimensional surface look more uniform, per can always be set to 1)
// 添加透视图
int diameter = 2 * radius;
float per = diameter / (diameter + rz3);
// 让我们为标签设置位置、比例和透明度
planetModel.setLoc2DX(rx3 * per);
planetModel.setLoc2DY(ry3 * per);
planetModel.setScale(per);
5. Touch event processing
- Single finger movement rotation (calculate the rotation distance according to the finger movement displacement)
// 单点触摸,旋转星球
float dx = event.getX() - downX;
float dy = event.getY() - downY;
if (isValidMove(dx, dy)) {
mAngleX = (dy / radius) * speed * TOUCH_SCALE_FACTOR;
mAngleY = (-dx / radius) * speed * TOUCH_SCALE_FACTOR;
processTouch();
downX = event.getX();
downY = event.getY();
}
Delayed execution
// 延时
handler.postDelayed(this, 30);
After the finger slides and rotates, decrease the rotation distance
// 减速模式(均速衰减)
if (mode == MODE_DECELERATE) {
if (Math.abs(mAngleX) > 0.2f) {
mAngleX -= mAngleX * 0.1f;
}
if (Math.abs(mAngleY) > 0.2f) {
mAngleY -= mAngleY * 0.1f;
}
}
processTouch();
- Two-finger zoom
Record the initial zoom ratio and the distance between the two fingers
if (event.getActionIndex() == 1) {
// 第二个触摸点
scaleX = getScaleX();
startDistance = distance(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
return true;
}
Calculate the zoom ratio when two fingers move, and limit the zoom ratio
// 双点触摸,缩放
float endDistance = distance(event.getX(0) - event.getX(1),
event.getY(0) - event.getY(1));
// 缩放比例
float scale = ((endDistance - startDistance) / (endDistance * 2) + 1) * scaleX;
if (scale > 1.4f) {
scale = 1.2f;
}
if (scale < 1) {
scale = 1f;
}
setScaleX(scale);
setScaleY(scale);
6. View and effect of each point
- The size of the point / the lightness and darkness of the
point The size of the point is scaled according to the perspective distance; the lightness and darkness are calculated according to the zoom - Pointed text too long marquee
// 位移
if (isOverstep) {
signDistanceX = signDistanceX + 2.5f;
if (signDistanceX > maxSignRange) {
signDistanceX = signWidth;
}
}
// 昵称文字过长(跑马灯)
if (isOverstep) {
canvas.drawText(sign, totalSignWidth - signDistanceX, signY, signPaint);
} else {
canvas.drawText(sign, signX, signY, signPaint);
}
- Point flashing effect
// 设置阴影
if (hasShadow) {
starPaint.setShadowLayer(shadowRadius, 1.0f, 1.0f, alpha);
canvas.drawCircle(starCenterX, starCenterY, radius, starPaint);
}
......
// 忽大忽小
if (hasShadow) {
if (isEnlarge) {
shadowRadius += radiusIncrement;
} else {
shadowRadius -= radiusIncrement;
}
if (shadowRadius < 1) {
shadowRadius = 1.0f;
isEnlarge = true;
} else if (shadowRadius > radius) {
shadowRadius = radius;
isEnlarge = false;
}
}
That's about it, just look at the code:
At last
If you have any comments and feedback, welcome to Github to raise an issue (I like others to raise issues) hahaha ~