gpt4 book ai didi

javascript - 碰撞后计算 Angular 速度

转载 作者:搜寻专家 更新时间:2023-10-31 22:05:33 25 4
gpt4 key购买 nike

我已经将冲突解决的线性分量降低得比较好,但是我无法弄清楚如何对角坐标进行同样的处理。根据我的阅读,它类似于... torque = point of collision x linear velocity。 (跨产品)我尝试将example I found合并到我的代码中,但是当对象碰撞时,我实际上根本看不到任何旋转。另一个小提琴与分离轴定理和角速度计算的基本实现完美配合。这是我想出的...

属性定义(方向,角速度和角加速度):

rotation: 0,
angularVelocity: 0,
angularAcceleration: 0

计算碰撞响应中的角速度:
var pivotA = this.vector(bodyA.x, bodyA.y);
bodyA.angularVelocity = 1 * 0.2 * (bodyA.angularVelocity / Math.abs(bodyA.angularVelocity)) * pivotA.subtract(isCircle ? pivotA.add(bodyA.radius) : {
x: pivotA.x + boundsA.width,
y: pivotA.y + boundsA.height
}).vCross(bodyA.velocity);
var pivotB = this.vector(bodyB.x, bodyB.y);
bodyB.angularVelocity = 1 * 0.2 * (bodyB.angularVelocity / Math.abs(bodyB.angularVelocity)) * pivotB.subtract(isCircle ? pivotB.add(bodyB.radius) : {
x: pivotB.x + boundsB.width,
y: pivotB.y + boundsB.height
}).vCross(bodyB.velocity);

在更新循环中更新方向:
var torque = 0;
torque += core.objects[o].angularVelocity * -1;
core.objects[o].angularAcceleration = torque / core.objects[o].momentOfInertia();
core.objects[o].angularVelocity += core.objects[o].angularAcceleration;
core.objects[o].rotation += core.objects[o].angularVelocity;

我将发布用于计算惯性矩的代码,但每个对象都有一个单独的代码,因此会有点...冗长。尽管如此,这里以一个圆圈为例:
return this.mass * this.radius * this.radius / 2;

为了显示结果,这是我的 fiddle。如图所示,物体在碰撞时不会旋转。 (在圆圈中不完全可见,但是它应该适用于零和七)

我究竟做错了什么?

编辑:之所以根本不旋转是因为响应函数中的组存在错误-它现在旋转,只是不正确。但是,我现在已经把它弄乱了,这已经注释掉了。

另外,我尝试了另一种旋转方法。这是响应中的代码:
_bodyA.angularVelocity = direction.vCross(_bodyA.velocity) / (isCircle ? _bodyA.radius : boundsA.width);
_bodyB.angularVelocity = direction.vCross(_bodyB.velocity) / (isCircle ? _bodyB.radius : boundsB.width);

注意 direction指的是“碰撞正常”。

最佳答案

力 vector 引起的角加速度和线性加速度

由于施加的力而产生的角加速度和方向加速度是同一事物的两个组成部分,不能分开。要得到一个,您需要同时解决。

定义计算

从简单的物理学和站在肩膀上,我们了解以下内容。

F is force (equivalent to inertia)
Fv is linear force
Fa is angular force
a is acceleration could be linear or rotational depending on where it is used
v is velocity. For angular situations it is the tangential component only
m is mass
r is radius

对于线性力
F = m * v 

从中我们得出
m = F / v
v = F / m

对于旋转力(v是切向速度)
F = r * r * m * (v / r) and simplify F = r * m * v

从中我们得出
m = F / ( r * v )
v = F / ( r * m )
r = F / ( v * m )

因为我们施加的力是瞬时的,所以我们可以将 a加速度和 v速度互换以给出以下所有公式

线性的
F = m * a  
m = F / a
a = F / m

旋转式
F = r * m * a
m = F / ( r * a )
a = F / ( r * m )
r = F / ( a * m )

因为我们只对线性和旋转解的速度变化感兴趣
a1 = F / m
a2 = F / ( r * m )

其中 a1是每帧2的像素加速度,而 a2是每帧2的弧度加速度(帧平方只是表示它是加速度)

从1D到2D

因为这是2D解决方案,而上面所有都是1D,所以我们需要使用 vector 。对于这个问题,我使用两种形式的2D vector 。具有大小(长度,距离等)和方向的极性。具有x和y的笛卡尔。 vector 代表什么取决于它的使用方式。

以下功能在解决方案中用作帮助程序。它们是用ES6编写的,因此对于不兼容的浏览器,您将不得不对其进行调整,尽管我不建议您使用它们,因为它们是为方便起见而编写的,它们效率低下,并且会进行大量冗余计算。

将 vector 从Polar转换为笛卡尔,返回一个新的 vector
function polarToCart(pVec, retV = {x : 0, y : 0}) {
retV.x = Math.cos(pVec.dir) * pVec.mag;
retV.y = Math.sin(pVec.dir) * pVec.mag;
return retV;
}

将 vector 从笛卡尔转换为极坐标,并返回一个新的 vector
function cartToPolar(vec, retV = {dir : 0, mag : 0}) {
retV.dir = Math.atan2(vec.y, vec.x);
retV.mag = Math.hypot(vec.x, vec.y);
return retV;
}

创建一个极 vector
function polar(mag = 1, dir = 0) {
return validatePolar({dir : dir,mag : mag});
}

将 vector 创建为笛卡尔
function vector(x = 1, y = 0) {
return {x : x, y : y};
}

正确的是arg vec是极性形式的 vector
function isPolar(vec) {
if (vec.mag !== undefined && vec.dir !== undefined) {return true;}
return false;
}

如果arg vec是笛卡尔形式的 vector ,则返回true
function isCart(vec) {
if (vec.x !== undefined && vec.y !== undefined) {return true;}
return false;
}

以极性形式返回新 vector ,还可以确保vec.mag为正
function asPolar(vec){
if(isCart(vec)){ return cartToPolar(vec); }
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
return { dir : vec.dir, mag : vec.mag };
}

复制未知的vec并将其转换为购物车(如果尚未转换)
function asCart(vec){
if(isPolar(vec)){ return polarToCart(vec); }
return { x : vec.x, y : vec.y};
}

计算可能会导致负振幅,尽管这对某些计算有效,这会导致不正确的 vector (反向),这仅验证极性 vector 具有正振幅,不会仅改变符号和方向就不会改变 vector
function validatePolar(vec) {
if (isPolar(vec)) {
if (vec.mag < 0) {
vec.mag = - vec.mag;
vec.dir += PI;
}
}
return vec;
}

盒子

现在,我们可以定义一个可以用来玩的对象。一个具有位置,大小,质量,方向,速度和旋转的简单盒子
function createBox(x,y,w,h){
var box = {
x : x, // pos
y : y,
r : 0.1, // its rotation AKA orientation or direction in radians
h : h, // its height
w : w, // its width
dx : 0, // delta x in pixels per frame 1/60th second
dy : 0, // delta y
dr : 0.0, // deltat rotation in radians per frame 1/60th second
mass : w * h, // mass in things
update :function(){
this.x += this.dx;
this.y += this.dy;
this.r += this.dr;
},
}
return box;
}

对对象施加力

现在我们可以重新定义一些术语

F(力)是 vector 力,大小是力,它具有方向
var force = polar(100,0); // create a force 100 units to the right (0 radians)

没有施加力的位置,力是没有意义的。

位置是一个 vector ,仅包含x和y位置
var location = vector(canvas.width/2, canvas.height/2);  // defines a point in the middle of the canvas

方向 vector 保持位置 vector 之间的方向和距离
var l1 = vector(canvas.width/2, canvas.height/2);  // defines a point in the middle of the canvas
var l2 = vector(100,100);
var direction = asPolar(vector(l2.x - l1.x, l2.y - l1.y)); // get the direction as polar vector
direction现在具有从 Canvas 中心到点(100,100)的方向以及距离。

我们要做的最后一件事是沿着方向 vector 从力 vector 中提取分量。当您向对象施加力时,该力被分为两部分,一个是沿线到对象中心的力并增加了对象的加速度,另一个力是与线到对象中心(切线)成90度的力那就是改变旋转的力。

要获得这两个分量,您需要获得力 vector 和方向 vector 之间的方向差异,力是从力施加到对象中心的位置得出的。
var force = polar(100,0);  // the force
var forceLoc = vector(50,50); // the location the force is applied

var direction2Center = asPolar(vector(box.x - forceLoc.x, box.y - forceLoc.y)); // get the direction as polar vector
var pheta = direction2Center - force.dir; // get the angle between the force and object center

现在您有了该 Angular pheta,可以使用力将力分为旋转分量和线性分量。
var F = force.mag; // get the force magnitude
var Fv = Math.cos(pheta) * F; // get the linear force
var Fa = Math.sin(pheta) * F; // get the angular force

现在可以将力转换回线性a = F / m和 Angular a = F /(m * r)的加速度
accelV = Fv / box.mass; // linear acceleration in pixels
accelA = Fa / (box.mass * direction2Center.mag); // angular acceleration in radians

然后,将线性力转换回以对象中心为方向的 vector
var forceV = polar(Fv, direction2Center);

转换返回到笛卡尔,因此我们可以将其添加到对象deltaX和deltaY中
forceV = asCart(forceV);

并将加速度添加到框中
box.dx += forceV.x;    
box.dy += forceV.y;

旋转加速度只是一维,因此只需将其添加到框的增量旋转中
box.dr += accelA;

就是这样。

用于对Box施加力的功能

该功能(如果附加到盒子上)将在盒子的某个位置施加力 vector 。

像这样附在盒子上
box.applyForce = applyForce; // bind function to the box;

然后您可以通过框调用该函数
box.applyForce(force, locationOfForce);


function applyForce(force, loc){ // force is a vector, loc is a coordinate
var toCenter = asPolar(vector(this.x - loc.x, this.y - loc.y)); // get the vector to the center
var pheta = toCenter.dir - force.dir; // get the angle between the force and the line to center
var Fv = Math.cos(pheta) * force.mag; // Split the force into the velocity force along the line to the center
var Fa = Math.sin(pheta) * force.mag; // and the angular force at the tangent to the line to the center
var accel = asPolar(toCenter); // copy the direction to center
accel.mag = Fv / this.mass; // now use F = m * a in the form a = F/m to get acceleration
var deltaV = asCart(accel); // convert acceleration to cartesian
this.dx += deltaV.x // update the box delta V
this.dy += deltaV.y //
var accelA = Fa / (toCenter.mag * this.mass); // for the angular component get the rotation
// acceleration from F=m*a*r in the
// form a = F/(m*r)
this.dr += accelA;// now add that to the box delta r
}

演示

该演示仅与 applyForce函数有关。与重力和弹跳有关的东西仅是非常差的近似值,不应将其用于任何物理类型的东西,因为它们不节省能量。

单击并拖动以沿鼠标移动的方向向对象施加力。

const PI90 = Math.PI / 2;
const PI = Math.PI;
const PI2 = Math.PI * 2;

const INSET = 10; // playfeild inset

const ARROW_SIZE = 6
const SCALE_VEC = 10;
const SCALE_FORCE = 0.15;
const LINE_W = 2;
const LIFE = 12;
const FONT_SIZE = 20;
const FONT = "Arial Black";
const WALL_NORMS = [PI90,PI,-PI90,0]; // dirction of the wall normals


var box = createBox(200, 200, 50, 100);
box.applyForce = applyForce; // Add this function to the box
// render / update function


var mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var i;
var mouse = {
x : 0, y : 0,buttonRaw : 0,
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];}
e.preventDefault();
}
mouse.start = function(element = document){
if(mouse.element !== undefined){ mouse.removeMouse();}
mouse.element = element;
mouse.mouseEvents.forEach(n => { element.addEventListener(n, mouseMove); } );
}
mouse.remove = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(n => { mouse.element.removeEventListener(n, mouseMove); } );
mouse.element = undefined;
}
}
return mouse;
})();


var canvas,ctx;
function createCanvas(){
canvas = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.left = "0px";
canvas.style.top = "0px";
canvas.style.zIndex = 1000;
document.body.appendChild(canvas);
}
function resizeCanvas(){
if(canvas === undefined){
createCanvas();
}
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if(box){
box.w = canvas.width * 0.10;
box.h = box.w * 2;
box.mass = box.w * box.h;
}
}

window.addEventListener("resize",resizeCanvas);
resizeCanvas();
mouse.start(canvas)





var tempVecs = [];
function addTempVec(v,vec,col,life = LIFE,scale = SCALE_VEC){tempVecs.push({v:v,vec:vec,col:col,scale:scale,life:life,sLife:life});}
function drawTempVecs(){
for(var i = 0; i < tempVecs.length; i ++ ){
var t = tempVecs[i]; t.life -= 1;
if(t.life <= 0){tempVecs.splice(i, 1); i--; continue}
ctx.globalAlpha = (t.life / t.sLife)*0.25;
drawVec(t.v, t.vec ,t.col, t.scale)
}
}
function drawVec(v,vec,col,scale = SCALE_VEC){
vec = asPolar(vec)
ctx.setTransform(1,0,0,1,v.x,v.y);
var d = vec.dir;
var m = vec.mag;
ctx.rotate(d);
ctx.beginPath();
ctx.lineWidth = LINE_W;
ctx.strokeStyle = col;
ctx.moveTo(0,0);
ctx.lineTo(m * scale,0);
ctx.moveTo(m * scale-ARROW_SIZE,-ARROW_SIZE);
ctx.lineTo(m * scale,0);
ctx.lineTo(m * scale-ARROW_SIZE,ARROW_SIZE);
ctx.stroke();
}
function drawText(text,x,y,font,size,col){
ctx.font = size + "px "+font;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.setTransform(1,0,0,1,x,y);
ctx.globalAlpha = 1;
ctx.fillStyle = col;
ctx.fillText(text,0,0);
}
function createBox(x,y,w,h){
var box = {
x : x, // pos
y : y,
r : 0.1, // its rotation AKA orientation or direction in radians
h : h, // its height, and I will assume that its depth is always equal to its height
w : w, // its width
dx : 0, // delta x in pixels per frame 1/60th second
dy : 0, // delta y
dr : 0.0, // deltat rotation in radians per frame 1/60th second
getDesc : function(){
var vel = Math.hypot(this.dx ,this.dy);
var radius = Math.hypot(this.w,this.h)/2
var rVel = Math.abs(this.dr * radius);
var str = "V " + (vel*60).toFixed(0) + "pps ";
str += Math.abs(this.dr * 60 * 60).toFixed(0) + "rpm ";
str += "Va " + (rVel*60).toFixed(0) + "pps ";

return str;
},
mass : function(){ return (this.w * this.h * this.h)/1000; }, // mass in K things
draw : function(){
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,this.x,this.y);
ctx.rotate(this.r);
ctx.fillStyle = "#444";
ctx.fillRect(-this.w/2, -this.h/2, this.w, this.h)
ctx.strokeRect(-this.w/2, -this.h/2, this.w, this.h)
},
update :function(){
this.x += this.dx;
this.y += this.dy;
this.dy += 0.061; // alittle gravity
this.r += this.dr;
},
getPoint : function(which){
var dx,dy,x,y,xx,yy,velocityA,velocityT,velocity;
dx = Math.cos(this.r);
dy = Math.sin(this.r);
switch(which){
case 0:
x = -this.w /2;
y = -this.h /2;
break;
case 1:
x = this.w /2;
y = -this.h /2;
break;
case 2:
x = this.w /2;
y = this.h /2;
break;
case 3:
x = -this.w /2;
y = this.h /2;
break;
case 4:
x = this.x;
y = this.y;
}
var xx,yy;
xx = x * dx + y * -dy;
yy = x * dy + y * dx;
var details = asPolar(vector(xx, yy))
xx += this.x;
yy += this.y;
velocityA = polar(details.mag * this.dr, details.dir + PI90);
velocityT = vectorAdd(velocity = vector(this.dx, this.dy), velocityA);
return {
velocity : velocity, // only directional
velocityT : velocityT, // total
velocityA : velocityA, // angular only
pos : vector(xx, yy),
radius : details.mag,
}
},
}
box.mass = box.mass(); // Mass remains the same so just set it with its function
return box;
}
// calculations can result in a negative magnitude though this is valide for some
// calculations this results in the incorrect vector (reversed)
// this simply validates that the polat vector has a positive magnitude
// it does not change the vector just the sign and direction
function validatePolar(vec){
if(isPolar(vec)){
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
}
return vec;
}
// converts a vector from polar to cartesian returning a new one
function polarToCart(pVec, retV = {x : 0, y : 0}){
retV.x = Math.cos(pVec.dir) * pVec.mag;
retV.y = Math.sin(pVec.dir) * pVec.mag;
return retV;
}
// converts a vector from cartesian to polar returning a new one
function cartToPolar(vec, retV = {dir : 0, mag : 0}){
retV.dir = Math.atan2(vec.y,vec.x);
retV.mag = Math.hypot(vec.x,vec.y);
return retV;
}
function polar (mag = 1, dir = 0) { return validatePolar({dir : dir, mag : mag}); } // create a polar vector
function vector (x= 1, y= 0) { return {x: x, y: y}; } // create a cartesian vector
function isPolar (vec) { if(vec.mag !== undefined && vec.dir !== undefined) { return true; } return false; }// returns true if polar
function isCart (vec) { if(vec.x !== undefined && vec.y !== undefined) { return true; } return false; }// returns true if cartesian
// copy and converts an unknown vec to polar if not already
function asPolar(vec){
if(isCart(vec)){ return cartToPolar(vec); }
if(vec.mag < 0){
vec.mag = - vec.mag;
vec.dir += PI;
}
return { dir : vec.dir, mag : vec.mag };
}
// copy and converts an unknown vec to cart if not already
function asCart(vec){
if(isPolar(vec)){ return polarToCart(vec); }
return { x : vec.x, y : vec.y};
}
// normalise makes a vector a unit length and returns it as a cartesian
function normalise(vec){
var vp = asPolar(vec);
vap.mag = 1;
return asCart(vp);
}
function vectorAdd(vec1, vec2){
var v1 = asCart(vec1);
var v2 = asCart(vec2);
return vector(v1.x + v2.x, v1.y + v2.y);
}
// This splits the vector (polar or cartesian) into the components along dir and the tangent to that dir
function vectorComponentsForDir(vec,dir){
var v = asPolar(vec); // as polar
var pheta = v.dir - dir;
var Fv = Math.cos(pheta) * v.mag;
var Fa = Math.sin(pheta) * v.mag;

var d1 = dir;
var d2 = dir + PI90;
if(Fv < 0){
d1 += PI;
Fv = -Fv;
}

if(Fa < 0){
d2 += PI;
Fa = -Fa;
}
return {
along : polar(Fv,d1),
tangent : polar(Fa,d2)
};
}

function doCollision(pointDetails, wallIndex){
var vv = asPolar(pointDetails.velocity); // Cartesian V make sure the velocity is in cartesian form
var va = asPolar(pointDetails.velocityA); // Angular V make sure the velocity is in cartesian form
var vvc = vectorComponentsForDir(vv, WALL_NORMS[wallIndex])
var vac = vectorComponentsForDir(va, WALL_NORMS[wallIndex])
vvc.along.mag *= 1.18; // Elastic collision requiers that the two equal forces from the wall
vac.along.mag *= 1.18; // against the box and the box against the wall be summed.
// As the wall can not move the result is that the force is twice
// the force the box applies to the wall (Yes and currently force is in
// velocity form untill the next line)
vvc.along.mag *= box.mass; // convert to force
//vac.along.mag/= pointDetails.radius
vac.along.mag *= box.mass
vvc.along.dir += PI; // force is in the oppisite direction so turn it 180
vac.along.dir += PI; // force is in the oppisite direction so turn it 180
// split the force into components based on the wall normal. One along the norm the
// other along the wall


vvc.tangent.mag *= 0.18; // add friction along the wall
vac.tangent.mag *= 0.18;
vvc.tangent.mag *= box.mass //
vac.tangent.mag *= box.mass
vvc.tangent.dir += PI; // force is in the oppisite direction so turn it 180
vac.tangent.dir += PI; // force is in the oppisite direction so turn it 180



// apply the force out from the wall
box.applyForce(vvc.along, pointDetails.pos)
// apply the force along the wall
box.applyForce(vvc.tangent, pointDetails.pos)
// apply the force out from the wall
box.applyForce(vac.along, pointDetails.pos)
// apply the force along the wall
box.applyForce(vac.tangent, pointDetails.pos)
//addTempVec(pointDetails.pos, vvc.tangent, "red", LIFE, 10)
//addTempVec(pointDetails.pos, vac.tangent, "red", LIFE, 10)

}


function applyForce(force, loc){ // force is a vector, loc is a coordinate
validatePolar(force); // make sure the force is a valid polar
// addTempVec(loc, force,"White", LIFE, SCALE_FORCE) // show the force
var l = asCart(loc); // make sure the location is in cartesian form
var toCenter = asPolar(vector(this.x - l.x, this.y - l.y));
var pheta = toCenter.dir - force.dir;
var Fv = Math.cos(pheta) * force.mag;
var Fa = Math.sin(pheta) * force.mag;
var accel = asPolar(toCenter); // copy the direction to center
accel.mag = Fv / this.mass; // now use F = m * a in the form a = F/m
var deltaV = asCart(accel); // convert it to cartesian
this.dx += deltaV.x // update the box delta V
this.dy += deltaV.y
var accelA = Fa / (toCenter.mag * this.mass); // for the angular component get the rotation
// acceleration
this.dr += accelA;// now add that to the box delta r
}

// make a box

ctx.globalAlpha = 1;
var lx,ly;
function update(){
// clearLog();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);

ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
ctx.fillStyle = "#888";
ctx.fillRect(INSET, INSET, canvas.width - INSET * 2, canvas.height - INSET * 2);
ctx.strokeRect(INSET, INSET, canvas.width - INSET * 2, canvas.height - INSET * 2);


ctx.lineWidth = 2;
ctx.strokeStyle = "black";

box.update();
box.draw();
if(mouse.buttonRaw & 1){
var force = asPolar(vector(mouse.x - lx, mouse.y - ly));
force.mag *= box.mass * 0.1;
box.applyForce(force,vector(mouse.x, mouse.y))
addTempVec(vector(mouse.x, mouse.y), asPolar(vector(mouse.x - lx, mouse.y - ly)), "Cyan", LIFE, 5);
}
lx = mouse.x;
ly = mouse.y;
for(i = 0; i < 4; i++){
var p = box.getPoint(i);
// only do one collision per frame or we will end up adding energy
if(p.pos.x < INSET){
box.x += (INSET) - p.pos.x;
doCollision(p,3)
}else
if( p.pos.x > canvas.width-INSET){
box.x += (canvas.width - INSET) - p.pos.x;
doCollision(p,1)
}else
if(p.pos.y < INSET){
box.y += (INSET) -p.pos.y;
doCollision(p,0)
}else
if( p.pos.y > canvas.height-INSET){
box.y += (canvas.height - INSET) -p.pos.y;
doCollision(p,2)
}


drawVec(p.pos,p.velocity,"blue")

}

drawTempVecs();
ctx.globalAlpha = 1;
drawText(box.getDesc(),canvas.width/2,FONT_SIZE,FONT,FONT_SIZE,"black");
drawText("Click drag to apply force to box",canvas.width/2,FONT_SIZE +17,FONT,14,"black");

requestAnimationFrame(update)


}



update();

关于javascript - 碰撞后计算 Angular 速度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36784456/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com