Three.js Custom空心圆柱几何

时间:2017-11-21 17:43:44

标签: javascript 3d three.js

我想为空心圆柱体创建自己的自定义three.js几何体。我尝试将RingGeometryCylinderGeometry类的部分组合起来取得了一些成功,但我仍然遇到了一些视觉错误:

  • 上限标准化错误(两个上限只有顶部纹理)
  • 躯干正常是错误的(内部躯干只能从里面看到)

这是一个例子。 (image used for capsSample Cylinder

这是代码

/**
 * 
 * @param {number} radius 
 * @param {number} holeRadius 
 * @param {number} height 
 * @param {number} segments 
 * @param {boolean} openEnded 
 * @param {number} thetaStart 
 * @param {number} thetaLength 
 */
function HollowCylinderGeometry(radius, holeRadius, height, segments, openEnded, thetaStart, thetaLength) {

    if (!(this instanceof HollowCylinderGeometry)) {
        throw new TypeError("HollowCylinderGeometry needs to be called using new");
    }

    THREE.Geometry.call(this);

    this.type = 'HollowCylinderGeometry';

    this.parameters = {
        radius: radius,
        holeRadius: holeRadius,
        height: height,
        segments: segments,
        openEnded: openEnded,
        thetaStart: thetaStart,
        thetaLength: thetaLength
    };

    this.fromBufferGeometry(new HollowCylinderBufferGeometry(radius, holeRadius, height, segments, openEnded, thetaStart, thetaLength));
    this.mergeVertices();

}

HollowCylinderGeometry.prototype = Object.create(THREE.Geometry.prototype);
HollowCylinderGeometry.prototype.constructor = HollowCylinderGeometry;

/**
 * 
 * @param {number} radius 
 * @param {number} holeRadius 
 * @param {number} height 
 * @param {number} segments 
 * @param {boolean} openEnded 
 * @param {number} thetaStart 
 * @param {number} thetaLength 
 */
function HollowCylinderBufferGeometry(radius, holeRadius, height, segments, openEnded, thetaStart, thetaLength) {

    if (!(this instanceof HollowCylinderBufferGeometry)) {
        throw new TypeError("HollowCylinderBufferGeometry needs to be called using new");
    }

    THREE.BufferGeometry.call(this);

    this.type = 'HollowCylinderBufferGeometry';

    this.parameters = {
        radius: radius,
        holeRadius: holeRadius,
        height: height,
        segments: segments,
        openEnded: openEnded,
        thetaStart: thetaStart,
        thetaLength: thetaLength
    };

    var scope = this;

    radius = !isNaN(radius) ? radius : 20;
    holeRadius = !isNaN(holeRadius) ? holeRadius : 20;
    height = !isNaN(height) ? height : 100;
    segments = !isNaN(segments = Math.floor(segments)) ? segments : 8;
    openEnded = !!openEnded;
    thetaStart = !isNaN(thetaStart) ? thetaStart : 0;
    thetaLength = !isNaN(thetaLength) ? thetaLength : Math.PI * 2;


    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var index = 0;
    var indexArray = [];
    var halfHeight = height / 2;
    var groupStart = 0;

    // generate geometry

    generateTorso(true);
    generateTorso(false);

    if (thetaLength % (Math.PI * 2) !== 0) {
        generateSide(true);
        generateSide(false);
    }

    if (!openEnded && radius > 0) {
        generateCap(true);
        generateCap(false);
    }

    // build geometry

    this.setIndex(indices);
    this.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    this.addAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
    this.addAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));

    function generateTorso(isOuter) {

        var x, y;
        var normal = new THREE.Vector3();
        var vertex = new THREE.Vector3();

        var groupCount = 0;

        var sign = isOuter ? 1 : -1;

        var activeRadius = isOuter ? radius : holeRadius;

        // this will be used to calculate the normal
        // generate vertices, normals and uvs


        // calculate the radius of the current row
        for (y = 0; y < 2; y++) {
            var indexRow = [];
            for (x = 0; x <= segments; x++) {

                var u = x / segments;

                var theta = u * thetaLength + thetaStart;

                var sinTheta = Math.sin(theta);
                var cosTheta = Math.cos(theta);

                // vertex

                vertex.x = activeRadius * sinTheta;
                vertex.y = -y * height + halfHeight;
                vertex.z = activeRadius * cosTheta;
                vertices.push(vertex.x, vertex.y, vertex.z);

                // normal

                normal.set(sinTheta, 0, cosTheta).normalize();
                normals.push(normal.x * sign, normal.y, normal.z * sign);

                // uv

                uvs.push(u, 1 - y);

                // save index of vertex in respective row

                indexRow.push(index++);

            }
            indexArray.push(indexRow);

        }

        // generate indices

        for (x = 0; x < segments; x++) {

            // we use the index array to access the correct indices
            var addSign = isOuter ? 0 : 2;
            var a = indexArray[addSign][x];
            var b = indexArray[addSign + 1][x];
            var c = indexArray[addSign + 1][x + 1];
            var d = indexArray[addSign][x + 1];

            // faces
            if (isOuter) {
                indices.push(a, b, d);
                indices.push(b, c, d);
            } else {
                indices.push(a, d, b);
                indices.push(b, d, c);
            }
            // update group counter

            groupCount += 6;


        }

        // add a group to the geometry. this will ensure multi material support

        scope.addGroup(groupStart, groupCount, 0);

        // calculate new start value for groups

        groupStart += groupCount;

    }

    /**
     * @returns {void}
     */
    function generateCap(isTop) {
        var indexStart = index;
        var segment = 0;

        var uv = new THREE.Vector2();

        var vertex = new THREE.Vector3();
        var sign = isTop ? 1 : -1;
        var groupCount = 0;

        for (var heightIndex = 0; heightIndex < 2; heightIndex++) {
            var activeRadius = heightIndex == 0 ? holeRadius : radius;
            for (var segmentIndex = 0; segmentIndex <= segments; segmentIndex++) {

                segment = segmentIndex / segments * thetaLength + thetaStart;

                // vertex

                vertex.x = activeRadius * Math.sin(segment);
                vertex.y = halfHeight * sign;
                vertex.z = activeRadius * Math.cos(segment);

                vertices.push(vertex.x, vertex.y, vertex.z);

                // normal

                normals.push(0, sign, 0);

                // uv

                uvs.push((vertex.x / radius + 1) / 2, (vertex.z / radius + 1) / 2);

                index++;
            }
        }

        // Generate Indices

        for (var segmentIndex = 0; segmentIndex < segments; segmentIndex++) {

            segment = segmentIndex + indexStart;

            var a = segment;
            var b = segment + segments + 1;
            var c = segment + segments + 2;
            var d = segment + 1;

            // faces
            if (isTop) {
                indices.push(a, b, d);
                indices.push(b, c, d);
            } else {
                indices.push(a, d, b);
                indices.push(b, d, c);
            }
            groupCount += 6;
        }

        scope.addGroup(groupStart, groupCount, 1);

        // calculate new start value for groups

        groupStart += groupCount;
    }

    function generateSide(isLeft) {

        var indexStart = index;
        var normal = new THREE.Vector3();
        var vertex = new THREE.Vector3();

        var theta = thetaStart;
        if (isLeft) theta += thetaLength;
        var sinTheta = Math.sin(theta);
        var cosTheta = Math.cos(theta);
        for (var y = 0; y < 2; y++) {
            for (var x = 0; x < 2; x++) {
                var activeRadius = x == 0 ? radius : holeRadius;
                vertex.x = activeRadius * sinTheta;
                vertex.y = halfHeight * (y == 0 ? -1 : 1);
                vertex.z = activeRadius * cosTheta;

                vertices.push(vertex.x, vertex.y, vertex.z);

                normal.set(sinTheta, 0, cosTheta).normalize();
                normals.push(normal.x, normal.y, normal.z);

                // uv

                uvs.push(1 - x, 1 - y);
                index++;
            }
        }

        var a = indexStart + 0;
        var b = indexStart + 1;
        var c = indexStart + 3;
        var d = indexStart + 2;

        // faces

        if (isLeft) {
            indices.push(a, b, d);
            indices.push(b, c, d);
        } else {
            indices.push(a, d, b);
            indices.push(b, d, c);
        }

        scope.addGroup(groupStart, 6, 0);

        // calculate new start value for groups

        groupStart += 6;
    }

}

HollowCylinderBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype);
HollowCylinderBufferGeometry.prototype.constructor = HollowCylinderBufferGeometry;

2 个答案:

答案 0 :(得分:1)

作为一种选择,让Three.js使用THREE.Shape()THREE.ExtrudeGeometry()为您完成工作。

enter image description here

&#13;
&#13;
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(0, 10, 20);
var renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x818181);
document.body.appendChild(renderer.domElement);

var controls = new THREE.OrbitControls(camera, renderer.domElement);

var loader = new THREE.TextureLoader();
loader.setCrossOrigin("");
var texture1 = loader.load("https://threejs.org/examples/textures/crate.gif");
texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
texture1.repeat.set(0.05, 0.05);
var texture2 = loader.load("https://threejs.org/examples/textures/hardwood2_diffuse.jpg");
texture2.wrapS = texture2.wrapT = THREE.RepeatWrapping;
texture2.repeat.set(0.1, 0.1);

var outerRadius = 10;
var innerRadius = 5;
var height = 2;

var arcShape = new THREE.Shape();
arcShape.moveTo(outerRadius * 2, outerRadius);
arcShape.absarc(outerRadius, outerRadius, outerRadius, 0, Math.PI * 2, false);
var holePath = new THREE.Path();
holePath.moveTo(outerRadius + innerRadius, outerRadius);
holePath.absarc(outerRadius, outerRadius, innerRadius, 0, Math.PI * 2, true);
arcShape.holes.push(holePath);

var geometry = new THREE.ExtrudeGeometry(arcShape, {
  amount: height,
  bevelEnabled: false,
  steps: 1,
  curveSegments: 60
});
geometry.center();
geometry.rotateX(Math.PI * -.5);
var mesh = new THREE.Mesh(geometry, [new THREE.MeshBasicMaterial({
  map: texture1
}), new THREE.MeshBasicMaterial({
  map: texture2
})]);
scene.add(mesh);

render();

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}
&#13;
body {
  overflow: hidden;
  margin: 0;
}
&#13;
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>
&#13;
&#13;
&#13;

答案 1 :(得分:1)

是否可以看到一张脸,取决于该图元是顺时针还是逆时针绘制。见Face Culling
你必须以相同的方向绘制所有的poligons(逆时针)。

更改功能generateTorso

if ( isOuter ) {
    indices.push(a, b, d);
    indices.push(b, c, d);
} else {
    indices.push(a, d, b);
    indices.push(b, d, c);
}

更改功能generateCap

if (isTop) {
    indices.push(a, b, d);
    indices.push(b, c, d);
} else {
    indices.push(a, d, b);
    indices.push(b, d, c);
}


预览:

enter image description here

请参阅代码段:

var renderer, scene, camera, controls;

function HollowCylinderGeometry(radius, holeRadius, height, segments, openEnded) {

    if (!(this instanceof HollowCylinderGeometry)) {
        throw new TypeError("HollowCylinderGeometry needs to be called using new");
    }

    THREE.Geometry.call(this);

    this.type = 'HollowCylinderGeometry';

    this.parameters = {
        radius: radius,
        holeRadius: holeRadius,
        height: height,
        segments: segments,
        openEnded: openEnded
    };

    this.fromBufferGeometry(new HollowCylinderBufferGeometry(radius, holeRadius, height, segments, openEnded));
    this.mergeVertices();

}

HollowCylinderGeometry.prototype = Object.create(THREE.Geometry.prototype);
HollowCylinderGeometry.prototype.constructor = HollowCylinderGeometry;

function HollowCylinderBufferGeometry(radius, holeRadius, height, segments, openEnded) {

    if (!(this instanceof HollowCylinderBufferGeometry)) {
        throw new TypeError("HollowCylinderBufferGeometry needs to be called using new");
    }

    THREE.BufferGeometry.call(this);

    this.type = 'HollowCylinderBufferGeometry';

    this.parameters = {
        radius: radius,
        holeRadius: holeRadius,
        height: height,
        segments: segments,
        openEnded: openEnded
    };

    var scope = this;

    radius = !isNaN(radius) ? radius : 20;
    height = !isNaN(radius) ? height : 100;

    segments = Math.floor(segments) || 8;

    openEnded = !!openEnded;

    // buffers

    var indices = [];
    var vertices = [];
    var normals = [];
    var uvs = [];

    // helper variables

    var index = 0;
    var indexArray = [];
    var halfHeight = height / 2;
    var groupStart = 0;

    // generate geometry

    generateTorso(true);
    generateTorso(false);

    if (!openEnded && radius > 0) {
        generateCap(true);
        generateCap(false);
    }

    // build geometry

    this.setIndex(indices);
    this.addAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    this.addAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));
    this.addAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2));

    function generateTorso(isOuter) {

        var x, y;
        var normal = new THREE.Vector3();
        var vertex = new THREE.Vector3();

        var groupCount = 0;

        var sign = isOuter ? 1 : -1;

        var activeRadius = isOuter ? radius : holeRadius;

        // this will be used to calculate the normal
        // generate vertices, normals and uvs


        // calculate the radius of the current row
        for (y = 0; y < 2; y++) {
            var indexRow = [];
            for (x = 0; x <= segments; x++) {

                var u = x / segments;

                var theta = u * Math.PI * 2;

                var sinTheta = Math.sin(theta);
                var cosTheta = Math.cos(theta);

                // vertex

                vertex.x = activeRadius * sinTheta;
                vertex.y = -y * height + halfHeight;
                vertex.z = activeRadius * cosTheta;
                vertices.push(vertex.x, vertex.y, vertex.z);

                // normal

                normal.set(sinTheta, 0, cosTheta).normalize();
                normals.push(normal.x * sign, normal.y, normal.z * sign);

                // uv

                uvs.push(u, 1 - y);

                // save index of vertex in respective row

                indexRow.push(index++);

            }
            indexArray.push(indexRow);

        }

        // generate indices

        for (x = 0; x < segments; x++) {

            // we use the index array to access the correct indices
            var addSign = isOuter ? 0 : 2;
            var a = indexArray[addSign][x];
            var b = indexArray[addSign + 1][x];
            var c = indexArray[addSign + 1][x + 1];
            var d = indexArray[addSign][x + 1];

            // faces

            if ( isOuter ) {
                indices.push(a, b, d);
                indices.push(b, c, d);
            } else {
                indices.push(a, d, b);
                indices.push(b, d, c);
            }

            // update group counter

            groupCount += 6;


        }

        // add a group to the geometry. this will ensure multi material support

        scope.addGroup(groupStart, groupCount, 0);

        // calculate new start value for groups

        groupStart += groupCount;

    }

    /**
     * @returns {void}
     */
    function generateCap(isTop) {
        var indexStart = index;
        var segment = 0;

        var uv = new THREE.Vector2();

        var vertex = new THREE.Vector3();
        var sign = isTop ? 1 : -1;
        var groupCount = 0;

        for (var heightIndex = 0; heightIndex < 2; heightIndex++) {
            var activeRadius = heightIndex == 0 ? holeRadius : radius;
            for (var segmentIndex = 0; segmentIndex <= segments; segmentIndex++) {

                segment = segmentIndex / segments * Math.PI * 2;

                // vertex

                vertex.x = activeRadius * Math.sin(segment);
                vertex.y = halfHeight * sign;
                vertex.z = activeRadius * Math.cos(segment);

                vertices.push(vertex.x, vertex.y, vertex.z);

                // normal

                normals.push(0, sign, 0);

                // uv

                uvs.push((vertex.x / radius + 1) / 2, (vertex.z / radius + 1) / 2);

                index++;
            }
        }

        // Generate Indices

        for (var segmentIndex = 0; segmentIndex < segments; segmentIndex++) {

            segment = segmentIndex + indexStart;

            var a = segment;
            var b = segment + segments + 1;
            var c = segment + segments + 2;
            var d = segment + 1;

            // faces

            if (isTop) {
                indices.push(a, b, d);
                indices.push(b, c, d);
            } else {
                indices.push(a, d, b);
                indices.push(b, d, c);
            }

            groupCount += 6;
        }

        scope.addGroup(groupStart, groupCount, 1);

        // calculate new start value for groups

        groupStart += groupCount;
    }

}

HollowCylinderBufferGeometry.prototype = Object.create(THREE.BufferGeometry.prototype);
HollowCylinderBufferGeometry.prototype.constructor = HollowCylinderBufferGeometry;

function init() {

    // renderer
    renderer = new THREE.WebGLRenderer();
    renderer.setSize( window.innerWidth, window.innerHeight );
    renderer.setClearColor(0x404040, 1);
    document.body.appendChild( renderer.domElement );

    // scene
    scene = new THREE.Scene();
    
    // camera
    camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
    camera.position.set( 3, 3, 3 );

    // controls
    controls = new THREE.OrbitControls( camera );

    var loader = new THREE.TextureLoader();
    loader.setCrossOrigin("");
    var texture1 = loader.load("https://threejs.org/examples/textures/hardwood2_diffuse.jpg");
    texture1.wrapS = texture1.wrapT = THREE.RepeatWrapping;
    texture1.repeat.set(2.0*Math.PI, 1.0);
    var texture2 = loader.load("https://threejs.org/examples/textures/crate.gif");
    texture2.wrapS = texture1.wrapT = THREE.RepeatWrapping;
    texture2.repeat.set(1.0, 1.0);
    
    // materials
    material_1 = new THREE.MeshBasicMaterial({
        map: texture1
        });
    material_2 = new THREE.MeshBasicMaterial({
        map: texture2
        });
    
    var geometry = new HollowCylinderGeometry(1.0, 0.3, 0.5, 16, false);
    var mesh = new THREE.Mesh(geometry, [material_1, material_2]);
    mesh.material.side = THREE.DoubleSide;
    
    // mesh
    scene.add( mesh );
}

function animate() {

    requestAnimationFrame( animate );
    renderer.render( scene, camera );
}

init();
animate();
body {
    margin: 0;
    overflow: hidden;
}

canvas {
    width: 100%;
    height: 100%
}
<script src="https://threejs.org/build/three.min.js"></script>
<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>