2

我正在研究一个基于六角瓷砖的行星生成器——想想投影到测地线球体上的 Civ5 地形。

下图是这种形状的上半部分:

我希望将这些单独的六边形和五边形压平成一个平面,尽管是一个不规则的平面,以便在反转转换和重组椭球体之前对其应用标准、正常的地形生成技术。

我将生成两个平面,北部和南部,因为本质上不可能展平任何真正的球体。

然而,鉴于可以在二维空间中显示六角网格,我认为没有理由不能制作这样的网格——尽管沿五边形有一些变形。

在我看来,我的步骤如下。

  1. 将每个图块生成为它自己的 THREE.Object3D,它本身包含一个具有 7 个顶点和 6 个面的网格(或者在 12 个五边形的情况下为 6 个和 5 个)
  2. 将每个图块(Object3D)的世界坐标中的纬度归一化为零(省略那些会落在“南半球”的纬度 - 这将是一个单独但相同的步骤)
  3. 旋转每个对象,使其内部网格与世界的地平线对齐——对象和内部网格的顶点是独立的,所以这很棘手。基本上,我需要每个网格顶点的 y 值为 0,而不会扭曲形状本身。
  4. 沿世界的水平面重新定位每个 Object3d,以使图块具有适当的间距,并且类似于六边形网格
  5. 将每个图块的每个顶点缝合成一个新的几何图形,一个连续的平面

一旦我有了它,我将应用我选择实施的任何世界建筑,将每个转换后的顶点“拾取”到它的原始图块上,然后取消转换它。瞧——一个十六进制世界。

构建六边形非常容易。我正在为 THREE.js 使用Rob Scanlon 的 Hexasphere 库。我如何实现图块的确切代码将在这篇文章的底部出现。

此外,标准化每个图块的纬度位置也相当简单。只需设置它的位置并完成。

我现在面临的问题是将六边形旋转到地平线上。我尝试使用标准三角运算。瓦片的顶点相对于它的中心点,而不是世界中心。因此,如果我取离瓦片中心的 x 距离最大的顶点,我可以计算瓦片(本身本质上是一个平面)相对于地平线形成的 x 平面中的角度。

# If center-point of tile is ( 0, 0, 0 ) relative to itself
# and
# Vertex with maximum x distance from center is ( x, y, z )
# then
# the x-distance from the center is x, and the y-distance is y, z-distance is z
# therefor
# I will always have two sides, the opposite and the adjacent, of any triangle I'm forming with sed point
#
# Using normal trig, I can calculate the angle

let rotationX = Math.atan( vertex.y / vertex.x )

我只是将该旋转反向应用于object3D(或者我想它是内部网格?)

这一直没有奏效,我无法塑造我错过了问题的至少一个关键部分的感觉。如果我在这件事上完全不合时宜,我不会感到惊讶。

接下来是适当地间隔瓷砖,这样它们的 xz 位置将类似于 2d 坐标而不是 3d 坐标。这一点,我也不知所措。

我意识到这可能没有实际意义。我想我可以简单地在球体上就地构建我的世界,但我很难将我的思想弯曲到第三维度。

代码如下。

AppAbstract.js - 包含场景、相机等。

class AppAbstract {

    constructor( positional, parameters ) {

        this.setupScene( parameters );

    };

    setupScene( parameters ) {

        this._scene = new THREE.Scene();

        this._camera = new THREE.PerspectiveCamera( 50, 2, 1, 100000 );
        this._camera.position.z = 2700;
        this._camera.position.y = 0;

        this._renderer = new THREE.WebGLRenderer( {
            canvas: parameters[ 'canvas' ],
            antialias: false, 
            alpha: true,
        } );

        this.renderer.setSize( window.innerWidth, window.innerWidth / 2 );
        this.renderer.setPixelRatio( window.devicePixelRatio );
        this.renderer.sortObjects = false;

        window.addEventListener( 'resize', () => {
            this.renderer.setSize( window.innerWidth, window.innerWidth / 2 );
        }, false );

        this.ambientLight = new THREE.AmbientLight( 0xffffff, 1 );
        this._scene.add( this.ambientLight );

        this._controls = new THREE.OrbitControls( this._camera, this._renderer.domElement );
        this.controls.enableDamping = true;
        this.controls.dampingFactor = 0.1;
        this.controls.rotateSpeed = 0.1;
        this.controls.autoRotate = false;
        this.controls.autoRotateSpeed = 0.01;
        this.controls.zoomSpeed = 0.1;

    };

    get scene() {

        return this._scene;

    };

    get camera() {

        return this._camera;

    };

    get renderer() {

        return this._renderer;

    };

    get controls() {

        return this._controls;

    };

};

PlanetApp.js -

    class PlanetApp extends AppAbstract {

    constructor( positional, parameters ) {
        super( positional, parameters );

        this.planet = new Planet( this, positional, parameters );
        this.scene.add( this.planet );

    };

};

行星.js

class Planet extends THREE.Object3D {

    constructor( app, positional, parameters ) {

        this.generateMaterials();
        this.generateTiles( positional[ 0 ] );

    };

    generateMaterials() {

        this.material = new THREE.MeshBasicMaterial( {
            color: 0x6495ED,
        } );

        this.wireframe = new THREE.MeshBasicMaterial( {
            color: 0x000000,
            wireframe: true,
        } );

    };

    generateTiles( subdivisions ) {

        let hexasphere = new Hexasphere( 1000, subdivisions, 0.99 );

        hexasphere.tiles.map( ( tile, index ) => {

            this.tiles.push( new Tile( tile, index ) );
            this.add( this.tiles[ index ] );

        } );

    };

}

Tile.js

class Tile extends THREE.Object3D {

    constructor( tile, index ) { super();

        this.index = index;

        this.getCenterPoint( tile );
        this.getOrdinalLatitude();

        this.generateMaterials();
        this.generateGeometry( tile );
        this.generateObjects();

    };

    getCenterPoint( tile ) {

        let [ xPositions, yPositions, zPositions ] = [ [], [], [] ];

        for ( let index = 0; index < tile.boundary.length; index++ ) {

            let vertex = tile.boundary[ index ];

            xPositions.push( parseFloat( vertex.x ) );
            yPositions.push( parseFloat( vertex.y ) );
            zPositions.push( parseFloat( vertex.z ) );

        };

        let centerX = xPositions.reduce( ( a, b ) => a + b, 0 );
        centerX = centerX / xPositions.length;

        let centerY = yPositions.reduce( ( a, b ) => a + b, 0 );
        centerY = centerY / yPositions.length;

        let centerZ = zPositions.reduce( ( a, b ) => a + b, 0 );
        centerZ = centerZ / zPositions.length;

        this.position.set( centerX, centerY, centerZ );

    };

    getOrdinalLatitude() {

        let boolean = this.position.y >= -10;

        let ordinal = ( boolean ) ? 'northern' : 'southern';
        this.ordinalLatitude = ordinal;

    };

    generateMaterials() {

        this.material = new THREE.MeshStandardMaterial( {
            color: 0x0077be,
        } );

        this.wireframe = new THREE.MeshStandardMaterial( {
            color: 0x000000,
            wireframe: true,
        } );

    };

    generateVertices( tile ) {

        this.geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );

        tile.boundary.map( ( point, index ) => {

            let xPosition = point.x - this.position.x;
            let yPosition = point.y - this.position.y;
            let zPosition = point.z - this.position.z;

            let vertex = new THREE.Vector3( 
                xPosition, yPosition, zPosition
             );

            this.geometry.vertices.push( vertex );

        } );

    };

    generateFaces() {

        this.geometry.faces.push( new THREE.Face3( 0, 1, 2 ) );
        this.geometry.faces.push( new THREE.Face3( 0, 2, 3 ) );
        this.geometry.faces.push( new THREE.Face3( 0, 3, 4 ) );
        this.geometry.faces.push( new THREE.Face3( 0, 4, 5 ) );

        if ( this.geometry.vertices.length == 6 ) {
            this.geometry.faces.push( new THREE.Face3( 0, 5, 1 ) );

        };

        if ( this.geometry.vertices.length == 7 ) {
            this.geometry.faces.push( new THREE.Face3( 0, 5, 6 ) );
            this.geometry.faces.push( new THREE.Face3( 0, 6, 1 ) );
        };

    };

    generateGeometry( tile ) {

        this.geometry = new THREE.Geometry();
        this.generateVertices( tile );
        this.generateFaces();

    };

    generateObjects( tile ) {

        var mesh = new THREE.Mesh( this.geometry, this.material );
        var frame = new THREE.Mesh( this.geometry, this.wireframe );
        this.add( mesh ); this.add( frame );

    };

    normalizeLatitude() {

        this.position.set( this.position.x, 0, this.position.z );

    };

    normalizeXRotation() {

        let furthestVertex = this.geometry.vertices.reduce( 
            ( a, b ) => a.x > b.x ? a : b
        );

        let radians = Math.atan( furthestVertex.z / furthestVertex.x );
        this.rotateX( -radians );

    };

    normalizeYRotation() {

        let furthestVertex = this.geometry.vertices.reduce( 
            ( a, b ) => a.y > b.y ? a : b
        );

        let radians = Math.atan( furthestVertex.z / furthestVertex.y );
        this.rotateY( -radians );

    };

    normalizeZRotation() {

        let furthestVertex = this.geometry.vertices.reduce( 
            ( a, b ) => a.z > b.z ? a : b
        );

        let radians = Math.atan( furthestVertex.z / furthestVertex.x );
        this.rotateZ( -radians );

    };

    normalizeRotation() {

        // this.normalizeXRotation();
        // this.normalizeYRotation();
        // this.normalizeZRotation();

    };

};

4

0 回答 0