Làm cách nào tôi có thể xoay đối tượng dựa trên phần bù của đối tượng khác?


25

Tôi có một mô hình 3D của một tháp pháo xoay quanh trục Y. Tháp pháo này có một khẩu pháo nằm ngoài trung tâm của vật thể. Tôi muốn pháo, không phải tháp pháo, nhắm vào một mục tiêu được chỉ định. Tuy nhiên, tôi chỉ có thể xoay tháp pháo, và do đó tôi không biết mình cần áp dụng phương trình nào để hoàn thành mục tiêu.

Hình ảnh sau đây minh họa vấn đề của tôi:enter image description here

Nếu tôi có tháp pháo "LookAt ()" mục tiêu, một tia laser phát ra từ khẩu pháo sẽ hoàn toàn bỏ lỡ mục tiêu đã nói.

Nếu đây là một kịch bản hoàn toàn từ trên xuống và khẩu pháo chính xác song song với tháp pháo, thì logic của tôi cho tôi biết rằng mục tiêu giả nên được đặt ở vị trí bằng với mục tiêu thực tế cộng với độ lệch bằng với giữa tháp pháo và đại bác. Tuy nhiên, trong kịch bản thực tế của tôi, máy ảnh của tôi bị góc 60 độ và khẩu pháo có một góc xoay nhẹ.

Hình ảnh sau đây minh họa kịch bản: Illustrative Scenario

Tôi không chắc chắn chính xác tại sao, nhưng nếu tôi áp dụng cùng một độ lệch đó, nó dường như chỉ hoạt động trong khi nhắm vào khoảng cách nhất định từ tháp pháo.

Là logic của tôi thiếu sót? Tôi có thiếu một cái gì đó cơ bản ở đây?

Chỉnh sửa cuối cùng: giải pháp được cung cấp bởi bản cập nhật mới nhất của @John Hamilton, giải quyết vấn đề này với độ chính xác hoàn hảo. Bây giờ tôi đã xóa mã và hình ảnh mà tôi đã sử dụng để minh họa việc triển khai không chính xác của mình.


Từ góc độ thiết kế vũ khí, bạn chỉ cần sửa súng ;)
Wayne Werner

@WayneWerner đây không phải là một lựa chọn trong trường hợp của tôi. Đó là một sự lựa chọn thiết kế để có được quanh co, nhưng chức năng.
Franconstein

1
Tôi đã thêm một ví dụ làm việc vào câu trả lời của tôi .
ens

Có vẻ như câu trả lời là hoàn hảo ... bạn có thể đề cập chính xác những chi tiết nào bạn cần không?
Seyed Morteza Kamali

Câu trả lời:


31

Câu trả lời thực sự khá dễ nếu bạn làm toán. Bạn có một khoảng cách cố định là Y và khoảng cách thay đổi là X (Xem hình 1). Bạn cần tìm ra góc giữa Z và X và xoay tháp pháo của bạn nhiều hơn nữa. nhập mô tả hình ảnh ở đây

Bước 1 - Nhận khoảng cách giữa đường tháp pháo (V) và đường súng (W) là Y (điều này không đổi nhưng không ảnh hưởng đến tính toán). Lấy khoảng cách từ tháp pháo đến mục tiêu (là X).

Bước 2 - Chia Y cho X và sau đó lấy Hyperbolic Sine của giá trị

double turnRadians = Mathf.Asin(Y/X);
double angle = Mathf.Rad2Deg * turnRadians;

//where B is the red dot, A is a point on the X line and C is a point on the Z line.

Bước 3 - Xoay tháp pháo nhiều hơn nữa (xung quanh trục đi từ đỉnh của nó xuống dưới cùng, rất có thể là trục lên nhưng chỉ bạn mới có thể biết phần đó).

gameObject.transform.Rotate(Vector3.up, turnAngle);

Tất nhiên trong trường hợp này, bạn cần nó để quay ngược chiều kim đồng hồ, do đó bạn có thể cần thêm một điểm trừ trước ngã rẽ ở đó, như trong -turnAngle.

Chỉnh sửa một số phần. Cảm ơn @ens đã chỉ ra sự khác biệt về khoảng cách.

OP cho biết khẩu súng của anh ta có một góc nên chúng ta đi, hình ảnh trước, giải thích sau: nhập mô tả hình ảnh ở đây

Chúng ta đã biết từ tính toán trước, nơi nhắm đường màu đỏ theo đường màu xanh. Vì vậy, nhắm đến dòng màu xanh đầu tiên:

float turnAngle = angleBetweenTurretAndTarget - angleBetweenTurretAndGun;
turret.transform.Rotate(Vector3.up, turnAngle);

Tính toán duy nhất khác ở đây, là tính toán của "X Prime" (X ') vì góc giữa súng và tháp pháo (góc "a") đã thay đổi khoảng cách giữa các đường.

//(this part had a mistake of using previous code inside new variable names, YPrime and Y are shown as X' and X in the 2nd picture.
float YPrime = Cos(a)*Y; //this part is what @ens is doing in his answer
double turnRadians = Mathf.Asin(YPrime/X);
double angle = Mathf.Rad2Deg * turnRadians;
turret.transform.Rotate(Vector3.up, angle);

Phần tiếp theo này là CHỈ cần thiết nếu bạn đang thực hiện mô-đun súng tháp pháo (tức là người dùng có thể thay đổi súng trên tháp pháo và các loại súng khác nhau có các góc khác nhau). Nếu bạn đang thực hiện việc này trong trình chỉnh sửa, bạn có thể thấy góc súng theo tháp pháo là gì.

Có hai phương pháp để tìm góc "a", một là phương thức Transform.up:

float angleBetween = Vector3.Angle(turret.transform.up, gun.transform.up);

Kỹ thuật trên sẽ tính toán trong 3D, vì vậy nếu bạn muốn có kết quả 2D, bạn cần loại bỏ trục Z (đó là điều tôi cho là trọng lực ở đâu, nhưng nếu bạn không thay đổi gì, thì trong Unity đó là trục Y lên hoặc xuống, tức là trọng lực nằm trên trục Y, vì vậy bạn có thể phải thay đổi mọi thứ):

Vector2 turretVector = new Vector2(turret.transform.up.x, turret.transform.up.y);
Vector2 gunVector = new Vector2(gun.transform.up.x, gun.transform.up.y);
float angleBetween = Vector2.Angle(turretVector, gunVector);

Cách thứ hai là phương pháp xoay vòng (tôi đang nghĩ về 2D trong trường hợp này):

double angleRadians = Mathf.Asin(turret.transform.rotation.z - gun.transform.rotation.z);
double angle = 2 * Mathf.Rad2Deg * angleRadians;

Một lần nữa, tất cả các mã này sẽ cung cấp cho bạn các giá trị dương, do đó bạn có thể phải cộng hoặc trừ số tiền tùy thuộc vào góc (cũng có các tính toán cho điều đó, nhưng tôi sẽ không đi sâu hơn). Một nơi tốt để bắt đầu điều này sẽ là Vector2.Dotphương pháp trong Unity.

Khối mã cuối cùng để giải thích thêm về những gì chúng tôi đang làm:

//turn turret towards target
turretTransform.up = targetTransform.position - turretTransform.position;
//adjust for gun angle
if (weaponTransform.localEulerAngles.z <180) //if the value is over 180 it's actually a negative for us
    turretTransform.Rotate(Vector3.forward, 90 - b - a);
else
    turretTransform.Rotate(Vector3.forward, 90 - b + a);

Nếu bạn đã làm mọi thứ đúng, bạn sẽ nhận được một cảnh như thế này ( liên kết cho gói unitypackage ): nhập mô tả hình ảnh ở đây Ý tôi là luôn luôn có giá trị dương:nhập mô tả hình ảnh ở đây

Phương thức Z có thể cho các giá trị âm:nhập mô tả hình ảnh ở đây

Đối với một cảnh ví dụ, hãy lấy unitypackage từ liên kết này .

Đây là mã tôi đã sử dụng trong cảnh (trên tháp pháo):

public class TurretAimCorrection : MonoBehaviour
{
    public Transform targetTransform;
    public Transform turretTransform;
    public Transform weaponTransform;

    private float f, d, x, y, h, b, a, weaponAngle, turnAngle;
    private void Start()
    {
        TurnCorrection();
    }

    private void Update()
    {
        TurnCorrection();
    }
    void TurnCorrection()
    {
        //find distances and angles
        d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.y), new Vector2(turretTransform.position.x, turretTransform.position.y));
        x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.y), new Vector2(weaponTransform.position.x, weaponTransform.position.y));
        weaponAngle = weaponTransform.localEulerAngles.z;
        weaponAngle = weaponAngle * Mathf.Deg2Rad;
        y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
        b = Mathf.Rad2Deg * Mathf.Acos(y / d);
        a = Mathf.Rad2Deg * Mathf.Acos(y / x);
        //turn turret towards target
        turretTransform.up = targetTransform.position - turretTransform.position;
        //adjust for gun angle
        if (weaponTransform.localEulerAngles.z < 180)
            turretTransform.Rotate(Vector3.forward, 90 - b - a);
        else
            turretTransform.Rotate(Vector3.forward, 90 - b + a);
        //Please leave this comment in the code. This code was made by 
        //http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR. 
        //This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
    }
}

Mã được điều chỉnh 3D với X và Z là mặt phẳng 2D:

public class TurretAimCorrection : MonoBehaviour
{
    public Transform targetTransform; //drag target here
    public Transform turretTransform; //drag turret base or turret top part here
    public Transform weaponTransform; //drag the attached weapon here

    private float d, x, y, b, a, weaponAngle, turnAngle;
    private void Start()
    {
        TurnAdjustment();
    }

    private void Update()
    {
        TurnAdjustment();
    }
    void TurnAdjustment()
    {

        d = Vector2.Distance(new Vector2(targetTransform.position.x, targetTransform.position.z), new Vector2(turretTransform.position.x, turretTransform.position.z));
        x = Vector2.Distance(new Vector2(turretTransform.position.x, turretTransform.position.z), new Vector2(weaponTransform.position.x, weaponTransform.position.z));
        weaponAngle = weaponTransform.localEulerAngles.y;
        weaponAngle = weaponAngle * Mathf.Deg2Rad;
        y = Mathf.Abs(Mathf.Cos(weaponAngle) * x);
        b = Mathf.Rad2Deg * Mathf.Acos(y / d);
        a = Mathf.Rad2Deg * Mathf.Acos(y / x);
        //turn turret towards target
        turretTransform.forward = new Vector3(targetTransform.position.x, 0, targetTransform.position.z) - new Vector3(turretTransform.position.x, 0, turretTransform.position.z);
        //adjust for gun angle
        if (weaponTransform.localEulerAngles.y < 180)
            turretTransform.Rotate(Vector3.up, - a +b-90);
        else
            turretTransform.Rotate(Vector3.up, + a+ b - 90);
        //Please leave this comment in the code. This code was made by 
        //http://gamedev.stackexchange.com/users/93538/john-hamilton a.k.a. CrazyIvanTR. 
        //This code is provided as is, with no guarantees. It has worked in local tests on Unity 5.5.0f3.
    }
}

Có một lỗ hổng nhỏ trong hình ảnh đầu tiên. Z là chiều dài của tháp pháo vào hộp. X là chiều dài của tháp pháo vào hộp sau khi quay ... x = z. Do đó, trừ khi y là cạnh huyền không phải là tam giác vuông và tội lỗi không được áp dụng.
Vịt lớn

@TheGreatDuck Z không phải là khoảng cách giữa tháp pháo và hộp là Vector2. Đối với tháp pháo đó (nó chỉ hiển thị hữu hạn thay vì có một mũi tên ở cuối). Ngay cả khi Z là khoảng cách, ảnh có đơn vị và bạn có thể thấy Z <X mà không cần tính toán.
John Hamilton

2
@Franconstein trước tiên bạn không phải xoay tháp pháo, sau đó áp dụng những cái này. Trước tiên, bạn có thể tính toán các giá trị này, sau đó thêm mức độ bạn nhận được từ các phương trình này vào mức độ lần lượt của tháp pháo. (Vì vậy, thay vì xoay tháp pháo 20 độ sang đối tượng, sau đó điều chỉnh cho súng, bạn sẽ xoay tháp pháo thêm 20 độ + điều chỉnh cho súng).
John Hamilton

@Franconstein Xem mã mới được điều chỉnh. Vì chúng tôi đã thay đổi các mặt phẳng, mã đã hoạt động khác với phiên bản khác. Tôi không biết tại sao điều này xảy ra nhưng nó vẫn hoạt động hoàn hảo vào lúc này. Xem: imgur.com/a/1scEH (loại bỏ các tháp pháo của bạn là không cần thiết, những mô hình đơn giản đó đã hoạt động giống như cách bạn đã làm).
John Hamilton

1
@ John John Hamilton Bạn đã làm điều đó! Cuối cùng nó cũng được giải quyết, và với độ chính xác giống như laser, quá! Cảm ơn bạn! Cảm ơn bạn! Cảm ơn bạn! Bây giờ tôi sẽ chỉnh sửa bài viết của mình như thế nào ngay từ đầu, để nó có thể dễ hiểu hơn để tham khảo trong tương lai! Một lần nữa, cảm ơn bạn!
Franconstein

3

Bạn cũng có thể sử dụng một cách tiếp cận tổng quát hơn:

Toán học cho vấn đề của bạn đã tồn tại ở dạng sản phẩm vô hướng (hoặc sản phẩm chấm) . Bạn chỉ cần có được hướng của trục vũ khí của bạn về phía trước và hướng từ vũ khí của bạn đến mục tiêu.

Đặt W là vector chuyển tiếp của vũ khí của bạn.

Đặt D là hướng từ vũ khí của bạn đến mục tiêu của bạn. (Target.pose - Weapon.pose)

Nếu bạn giải quyết công thức của sản phẩm chấm

dot(A,B) = |A|*|B|*cos(alpha) with alpha = the angle between A and B

cho alpha, bạn nhận được:

              ( dot(W,D) )
alpha = arccos(--------- )
              ( |W|*|D|  )

Bạn chỉ phải chuyển đổi radian sang độ và bạn có góc của mình để xoay robot. (Như bạn đã đề cập, vũ khí nằm ở một góc với robot của bạn, vì vậy bạn cần thêm góc vào alpha)

nhập mô tả hình ảnh ở đâynhập mô tả hình ảnh ở đây


2

Tất cả các câu trả lời được đăng cho đến nay là (ít nhiều) sai, vì vậy đây là một giải pháp đúng nhanh chóng:

nhập mô tả hình ảnh ở đây

Để nhắm súng về phía mục tiêu, xoay vectơ về phía trước của tháp pháo tới mục tiêu và thêm góc.

Vì vậy, hãy tìm:

   d / sin(δ) = a / sin(α) # By the Law of Sines
=> α = asin(a * sin(δ) / d)

   β = 180° - α - δ
   θ = 90° - β
=> θ = α + δ - 90°
     = asin(a * sin(δ) / d) + δ - 90°
     = asin(a * cos') / d) - δ' # Where (δ' = 90° - δ) is the angle between 
                                  # the gun and the turret forward vector.

Khi δ' = 0điều này đơn giản hóa θ = asin(a / d), phù hợp với phần đầu tiên trong câu trả lời của John Hamilton.

Chỉnh sửa:

Tôi đã thêm một ví dụ làm việc.

Mở trong JSFiddle hoặc sử dụng đoạn mã được nhúng bên dưới:

var Degree = Math.PI / 180;
var showDebugOverlays = false;

var Turret = {
    gunAngle: -10 * Degree,
    maxGunAngle: 85 * Degree,
    adaptedGunXOffset: null,

    rotateToTarget: function (target) {
        var delta = Vec.subtract(this.position, target.position);
        var dist = Vec.length(delta);
        var angle = Vec.angle(delta);

        theta = Math.asin(this.adaptedGunXOffset / dist) + this.gunAngle;
        this.rotation = -(angle + theta);

        this.updateGunRay(target);
    },

    setGunAngle: function (angle) {
        var angle = this.clampGunAngle(angle);
        this.gunAngle = angle;
        this.gun.rotation = angle;
        // Account for the fact that the origin of the gun also has an y offset
        // relative to the turret origin
        var extraXOffset = this.gun.position.y * Math.tan(angle);
        var gunXOffset = this.gun.position.x + extraXOffset;
        // This equals "a * cos(δ')" in the angle formula
        this.adaptedGunXOffset = gunXOffset * Math.cos(-angle);

        if (showDebugOverlays) {
            // Show x offsets
            this.removeChild(this.xOffsetOverlay);
            this.removeChild(this.extraXOffsetOverlay);
            this.xOffsetOverlay = addRect(this, 0, 0, this.gun.position.x, 1, 0xf6ff00);
            this.extraXOffsetOverlay = addRect(this, this.gun.position.x, 0, extraXOffset, 1, 0xff00ae);
        }
    },

    rotateGun: function (angleDelta) {
        this.setGunAngle(this.gunAngle + angleDelta);
    },

    updateGunRay: function (target) {
        var delta = this.gun.toLocal(target.position);
        var dist = Vec.length(delta);
        this.gun.removeChild(this.gun.ray);
        this.gun.ray = makeLine(0, 0, 0, -dist);
        this.gun.addChildAt(this.gun.ray, 0);
    },

    clampGunAngle: function (angle) {
        if (angle > this.maxGunAngle) {
            return this.maxGunAngle;
        }
        if (angle < -this.maxGunAngle) {
            return -this.maxGunAngle;
        }
        return angle;
    }
}

function makeTurret() {
    var turret = new PIXI.Sprite.fromImage('http://i.imgur.com/gPtlPJh.png');
    var gunPos = new PIXI.Point(25, -25)

    turret.anchor.set(0.5, 0.5);

    var gun = new PIXI.Container();
    var gunImg = new PIXI.Sprite.fromImage('http://i.imgur.com/RE45GEY.png');
    gun.ray = makeLine(0, 0, 0, -250);
    gun.addChild(gun.ray);

    gunImg.anchor.set(0.5, 0.6);
    gun.addChild(gunImg);
    gun.position = gunPos;

    // Turret forward vector
    turret.addChild(makeLine(0, -38, 0, -90, 0x38ce2c));
    turret.addChild(gun);
    turret.gun = gun;

    Object.setPrototypeOf(Turret, Object.getPrototypeOf(turret));
    Object.setPrototypeOf(turret, Turret);

    turret.setGunAngle(turret.gunAngle);

    if (showDebugOverlays) {
        addRect(turret, 0, 0, 1, 1); // Show turret origin
        addRect(gun, -1, 1, 2, 2, 0xff0096); // Show gun origin
    }

    return turret;
}

function makeTarget() {
    var target = new PIXI.Graphics();
    target.beginFill(0xd92f8f);
    target.drawCircle(0, 0, 9);
    target.endFill();
    return target;
}

var CursorKeys = {
    map: { ArrowLeft: -1, ArrowRight: 1 },
    pressedKeyDirection: null,

    onKeyDown: function (keyEvent) {
        var key = this.map[keyEvent.key];
        if (key) {
            this.pressedKeyDirection = key;
        }
    },
    onKeyUp: function (keyEvent) {
        var key = this.map[keyEvent.key];
        if (key) {
            if (this.pressedKeyDirection == key) {
                this.pressedKeyDirection = null;
            }
        }
    }
}

document.body.addEventListener("keydown", CursorKeys.onKeyDown.bind(CursorKeys));
document.body.addEventListener("keyup", CursorKeys.onKeyUp.bind(CursorKeys));

function makeLine(x1, y1, x2, y2, color) {
    if (color == undefined) {
        color = 0x66CCFF;
    }
    var line = new PIXI.Graphics();
    line.lineStyle(1.5, color, 1);
    line.moveTo(x1, y1);
    line.lineTo(x2, y2);
    return line;
}

function addRect(parent, x, y, w, h, color) {
    if (color == undefined) {
        color = 0x66CCFF;
    }
    var rectangle = new PIXI.Graphics();
    rectangle.beginFill(color);
    rectangle.drawRect(x, y, w, h);
    rectangle.endFill();
    parent.addChild(rectangle);
    return rectangle;
}

var Vec = {
    subtract: function (a, b) {
        return { x: a.x - b.x,
                 y: a.y - b.y };
    },
    length: function (v) {
        return Math.sqrt(v.x * v.x + v.y * v.y);
    },
    angle: function (v) {
        return Math.atan2(v.x, v.y)
    }
}

Math.clamp = function(n, min, max) {
    return Math.max(min, Math.min(n, max));
}

var renderer;
var stage;
var turret;
var target;

function run() {
    renderer = PIXI.autoDetectRenderer(600, 300, { antialias: true });
    renderer.backgroundColor = 0x2a2f34;
    document.body.appendChild(renderer.view);
    stage = new PIXI.Container();
    stage.interactive = true;

    target = makeTarget();
    target.position = { x: renderer.width * 0.2, y: renderer.height * 0.3 };

    turret = makeTurret();
    turret.position = { x: renderer.width * 0.65, y: renderer.height * 0.3 };
    turret.rotateToTarget(target);

    stage.addChild(turret);
    stage.addChild(target);

    var message = new PIXI.Text(
        "Controls: Mouse, left/right cursor keys",
        {font: "18px Arial", fill: "#7c7c7c"}
    );
    message.position.set(10, 10);
    stage.addChild(message);

    stage.on('mousemove', function(e) {
        var pos = e.data.global;
        target.position.x = Math.clamp(pos.x, 0, renderer.width);
        target.position.y = Math.clamp(pos.y, 0, renderer.height);
        turret.rotateToTarget(target);
    })

    animate();
}

function animate() {
    requestAnimationFrame(animate);

    if (CursorKeys.pressedKeyDirection) {
        turret.rotateGun(3 * Degree * CursorKeys.pressedKeyDirection);
        turret.rotateToTarget(target);
    }

    renderer.render(stage);
}

run();
body {
    padding: 0;
    margin: 0;
}

#capture_focus {
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.2.2/pixi.min.js"></script>
<div id="capture_focus" />


Cảm ơn bạn rất nhiều vì lời giải thích này. Nó đủ đơn giản để tôi hiểu, và nó dường như tính đến mọi tình huống. Tuy nhiên, khi tôi thực hiện nó, kết quả tôi đạt được không thuận lợi. Tôi đã chỉnh sửa bài viết gốc của mình để bao gồm mã của mình, một hình ảnh trực quan hóa thiết lập của tôi và kết quả cho từng biến. Vectơ phía trước của tháp pháo của tôi luôn nhìn vào mục tiêu, nhưng ngay cả khi không, kết quả vẫn gần như giữ nguyên. Tôi có làm điều gì sai? Nó có phải là mã của tôi không?
Franconstein

Nếu các câu trả lời khác "ít nhiều sai", bạn không hiểu / thực hiện chúng một cách chính xác. Trước đây, tôi đã sử dụng cả hai câu trả lời thay thế để tạo hành vi mong muốn. @Franconstein, tôi thậm chí còn thấy những bình luận của bạn về ít nhất một điều để nói rằng bạn đã xác minh rằng nó hoạt động. Nếu bạn đã xác minh một giải pháp, bạn vẫn có vấn đề?
Gnemlock

@Gnemlock, giải pháp của John Hamilton không sai - Tôi đã triển khai nó và nó đã hoạt động, và do đó tôi đã xác minh giải pháp của mình như đã được phê duyệt. Nhưng sau khi thực hiện nó, tôi bắt đầu thử các kịch bản không tĩnh khác nhau và giải pháp không theo kịp. Mặc dù vậy, tôi không muốn loại bỏ nó sớm, vì vậy tôi đã đi qua nó với một đồng nghiệp. Chúng tôi cuối cùng đã xác nhận nó không giữ, nhưng bây giờ đã đăng một giải pháp khả thi khác và John đã chỉnh sửa bài đăng của mình để đưa nó vào. Cho đến thời điểm này, tôi không thể xác nhận một trong hai hoạt động chính xác và vẫn đang cố gắng. Tôi đã đăng mã của tôi để xem nếu nó giúp. Tôi đã làm sai?
Franconstein

@Franconstein, trong hình thức này nó quá khó hiểu. Tôi muốn nói ví dụ này là một ví dụ điển hình về những gì bạn mong đợi khi đọc sách giáo khoa Toán học , nhưng nó quá khó hiểu liên quan đến lập trình trò chơi nói chung. Yếu tố quan trọng duy nhất là góc (mà câu trả lời ban đầu mà John Hamilton đã đăng đã cung cấp). Tôi thấy những gì bạn có ý nghĩa bởi các góc độ cụ thể, cuối cùng bạn có thể đã làm điều này không chính xác. Tôi thấy có rất nhiều phòng, trong câu trả lời này, để làm điều đó không chính xác .
Gnemlock
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.