├── .gitignore ├── .npmignore ├── package.json ├── README.md ├── test.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.log 4 | .DS_Store 5 | bundle.js 6 | test 7 | test.js 8 | demo/ 9 | bower.json -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-rounded-box", 3 | "version": "0.1.0", 4 | "description": "Box geometry for three.js with filleted edges", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "wzrd test.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/pailhead/three-rounded-box.git" 12 | }, 13 | "keywords": [ 14 | "threejs", 15 | "geometry", 16 | "3d", 17 | "javascript", 18 | "box", 19 | "rounded", 20 | "smooth" 21 | ], 22 | "author": "Dusan Bosnjak", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/pailhead/three-rounded-box/issues" 26 | }, 27 | "homepage": "https://github.com/pailhead/three-rounded-box#readme", 28 | "dependencies": {}, 29 | "devDependencies": { 30 | "browserify": "^8.1.1", 31 | "canvas-testbed": "^1.0.3", 32 | "scriptjs": "^2.5.7", 33 | "three": "^0.82.1", 34 | "three-fps-counter": "^0.2.0", 35 | "wzrd": "^1.2.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-rounded-box 2 | [![stable](http://badges.github.io/stability-badges/dist/stable.svg)](http://github.com/badges/stability-badges) 3 | 4 | See it [live](http://dusanbosnjak.com/test/webGL/three-rounded-box/). 5 | 6 | A geometry class with filleted edges for [THREE.js](https://github.com/mrdoob/three.js/). Extends `THREE.BufferGeometry`. 7 | 8 | 9 | ## Constructor 10 | 11 | **RoundedBoxGeometry( *width* , *height* , *depth* , *radius* , *radiusSegments* )** 12 | 13 | ``` 14 | width = Float //size of box in x axis, default 1 15 | height = Float //size of box in y axis, default 1 16 | depth = Float //size of box in z axis, default 1 17 | radius = Float //radius of the fillet, default 0.15 18 | radiusSegments = Int //segments along the fillet, default 1 19 | ``` 20 | 21 | 22 | ## Usage 23 | 24 | [![NPM](https://nodei.co/npm/three-rounded-box.png)](https://npmjs.org/package/three-rounded-box) 25 | 26 | ```javascript 27 | 28 | var RoundedBoxGeometry = require('three-rounded-box')(THREE); //pass your instance of three 29 | 30 | var myBox = new THREE.Mesh( new RoundedBoxGeometry( 10 , 10 , 10 , 2 , 5 ) ); 31 | 32 | myScene.add(myBox); 33 | 34 | ``` 35 | 36 | 37 | ## Test 38 | 39 | ``` 40 | npm install 41 | npm start 42 | ``` 43 | 44 | You should see a box with smooth edges spinning. 45 | 46 | 47 | ## TODO 48 | - cleanup the weaving logic 49 | - add segments along the sides 50 | 51 | 52 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | var createApp = require('canvas-testbed') 2 | 3 | var THREE = require('three') 4 | var RoundedBoxGeometry = require('./')(THREE) 5 | 6 | var FPSCounter = require('three-fps-counter')(THREE); 7 | 8 | createApp(render, start, { 9 | context: 'webgl', 10 | onResize: resize 11 | }); 12 | 13 | var renderer, 14 | scene, 15 | camera, 16 | controls, 17 | screenQuad, 18 | box, 19 | fps 20 | ; 21 | 22 | function start(gl, width, height) { 23 | 24 | renderer = new THREE.WebGLRenderer({ 25 | canvas: gl.canvas 26 | }); 27 | 28 | scene = new THREE.Scene(); 29 | 30 | camera = new THREE.PerspectiveCamera(50, width/height, 1, 1000); 31 | camera.position.set( 10, 10 , 10); 32 | camera.lookAt(new THREE.Vector3()); 33 | 34 | box = new THREE.Mesh( new RoundedBoxGeometry( 2 , 2 , 2 , .25 , 3 ) , new THREE.MeshPhongMaterial({color:'red'})); 35 | 36 | scene.add(box); 37 | 38 | var dLight = new THREE.DirectionalLight(0xffffff,1); 39 | dLight.position.set( -10,10,10 ); 40 | 41 | scene.add( dLight ); 42 | 43 | fps = new FPSCounter( renderer ); 44 | 45 | fps.setScreenSize( renderer.getSize().width , renderer.getSize().height ); 46 | 47 | } 48 | 49 | function render(gl, width, height) { 50 | 51 | 52 | box.rotation.y += 0.02; 53 | 54 | renderer.render(scene, camera); 55 | 56 | fps.render(); 57 | } 58 | 59 | function resize(width, height) { 60 | if (!renderer) 61 | return; 62 | 63 | 64 | renderer.setViewport(0, 0, width, height); 65 | camera.aspect = width/height; 66 | camera.updateProjectionMatrix(); 67 | fps.setScreenSize( width , height ); 68 | 69 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author pailhead / http://dusanbosnjak.com 3 | * @author benolayinka / github.com/benolayinka 4 | */ 5 | module.exports = function( THREE ){ 6 | 7 | RoundedBoxGeometry = function ( 8 | width , 9 | height , 10 | depth , 11 | radius, 12 | radiusSegments 13 | ) { 14 | 15 | THREE.BufferGeometry.call( this ); 16 | 17 | this.type = 'RoundedBoxGeometry'; 18 | 19 | 20 | //validate params =================================== 21 | 22 | radiusSegments = !isNaN( radiusSegments ) ? Math.max( 1, Math.floor( radiusSegments ) ) : 1 ; 23 | 24 | width = !isNaN(width) ? width : 1; 25 | height = !isNaN(height) ? height : 1; 26 | depth = !isNaN(depth) ? depth : 1; 27 | 28 | radius = !isNaN(radius) ? radius : .15; 29 | 30 | radius = Math.min( radius , Math.min( width , Math.min( height , Math.min( depth ) ) ) / 2 ); 31 | 32 | var edgeHalfWidth = width / 2 - radius; 33 | var edgeHalfHeight = height / 2 - radius; 34 | var edgeHalfDepth = depth / 2 - radius; 35 | 36 | 37 | //not sure why this is needed, for querying? ======== 38 | 39 | this.parameters = { 40 | width: width , 41 | height: height , 42 | depth: depth , 43 | radius: radius, 44 | radiusSegments: radiusSegments 45 | }; 46 | 47 | 48 | //calculate vertices count ========================== 49 | 50 | var rs1 = radiusSegments + 1; //radius segments + 1 51 | 52 | var totalVertexCount = ( rs1 * radiusSegments + 1 ) << 3; 53 | 54 | 55 | //make buffers ====================================== 56 | 57 | var positions = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 ); 58 | 59 | var normals = new THREE.BufferAttribute( new Float32Array( totalVertexCount * 3 ), 3 ); 60 | 61 | 62 | //some vars ========================================= 63 | 64 | var 65 | cornerVerts = [], 66 | cornerNormals = [], 67 | normal = new THREE.Vector3(), 68 | vertex = new THREE.Vector3(), 69 | vertexPool = [], 70 | normalPool = [], 71 | indices = [] 72 | ; 73 | 74 | var 75 | lastVertex = rs1 * radiusSegments, 76 | cornerVertNumber = rs1 * radiusSegments + 1 77 | ; 78 | 79 | doVertices(); 80 | doFaces(); 81 | doCorners(); 82 | doHeightEdges(); 83 | doWidthEdges(); 84 | doDepthEdges() 85 | 86 | // calculate vert positions ========================= 87 | 88 | function doVertices(){ 89 | 90 | //corner offsets 91 | var cornerLayout = [ 92 | new THREE.Vector3( 1 , 1 , 1 ), 93 | new THREE.Vector3( 1 , 1 , -1 ), 94 | new THREE.Vector3( -1 , 1 , -1 ), 95 | new THREE.Vector3( -1 , 1 , 1 ), 96 | new THREE.Vector3( 1 , -1 , 1 ), 97 | new THREE.Vector3( 1 , -1 , -1 ), 98 | new THREE.Vector3( -1 , -1 , -1 ), 99 | new THREE.Vector3( -1 , -1 , 1 ) 100 | ]; 101 | 102 | //corner holder 103 | for ( var j = 0 ; j < 8 ; j ++ ){ 104 | 105 | cornerVerts.push([]); 106 | cornerNormals.push([]); 107 | 108 | } 109 | 110 | //construct 1/8 sphere ============================== 111 | 112 | var PIhalf = Math.PI / 2; 113 | 114 | var cornerOffset = new THREE.Vector3( edgeHalfWidth , edgeHalfHeight , edgeHalfDepth ); 115 | 116 | for ( var y = 0; y <= radiusSegments; y ++ ) { 117 | 118 | var v = y / radiusSegments; 119 | 120 | var va = v * PIhalf; //arrange in 90 deg 121 | 122 | var cosVa = Math.cos( va ); //scale of vertical angle 123 | 124 | var sinVa = Math.sin( va ); 125 | 126 | if( y == radiusSegments ){ 127 | 128 | vertex.set( 0 , 1 , 0 ); 129 | 130 | var vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset ); 131 | 132 | cornerVerts[0].push( vert ); 133 | 134 | vertexPool.push( vert ); 135 | 136 | var norm = vertex.clone(); 137 | 138 | cornerNormals[0].push( norm ); 139 | 140 | normalPool.push( norm ); 141 | 142 | continue; //skip row loop 143 | 144 | } 145 | 146 | for ( var x = 0; x <= radiusSegments; x ++ ) { 147 | 148 | var u = x / radiusSegments; 149 | 150 | var ha = u * PIhalf; 151 | 152 | //make 1/8 sphere points 153 | vertex.x = cosVa * Math.cos( ha ); 154 | vertex.y = sinVa; 155 | vertex.z = cosVa * Math.sin( ha ); 156 | 157 | //copy sphere point, scale by radius, offset by half whd 158 | var vert = vertex.clone().multiplyScalar( radius ).add( cornerOffset ); 159 | 160 | cornerVerts[0].push( vert ); 161 | 162 | vertexPool.push( vert ); 163 | 164 | //sphere already normalized, just clone 165 | 166 | var norm = vertex.clone().normalize(); 167 | cornerNormals[0].push( norm ); 168 | normalPool.push( norm ); 169 | 170 | } 171 | 172 | } 173 | 174 | //distribute corner verts =========================== 175 | 176 | for ( var i = 1 ; i < 8 ; i ++ ){ 177 | 178 | for( var j = 0 ; j < cornerVerts[0].length ; j ++ ){ 179 | 180 | var vert = cornerVerts[0][j].clone().multiply( cornerLayout[i] ); 181 | 182 | cornerVerts[i].push( vert ); 183 | 184 | vertexPool.push( vert ); 185 | 186 | var norm = cornerNormals[0][j].clone().multiply( cornerLayout[i] ); 187 | 188 | cornerNormals[i].push( norm ); 189 | 190 | normalPool.push( norm ); 191 | 192 | } 193 | 194 | } 195 | 196 | } 197 | 198 | 199 | // weave corners ==================================== 200 | 201 | function doCorners(){ 202 | 203 | var indexInd = 0; 204 | 205 | 206 | var flips = [ 207 | true, 208 | false, 209 | true, 210 | false, 211 | false, 212 | true, 213 | false, 214 | true 215 | ]; 216 | 217 | var lastRowOffset = rs1 * ( radiusSegments - 1 ); 218 | 219 | for ( var i = 0 ; i < 8 ; i ++ ){ 220 | 221 | var cornerOffset = cornerVertNumber * i; 222 | 223 | for ( var v = 0 ; v < radiusSegments - 1 ; v ++ ){ 224 | 225 | var r1 = v * rs1; //row offset 226 | var r2 = (v + 1) * rs1; //next row 227 | 228 | for ( var u = 0 ; u < radiusSegments ; u ++ ){ 229 | 230 | var u1 = u + 1; 231 | var a = cornerOffset + r1 + u; 232 | var b = cornerOffset + r1 + u1; 233 | var c = cornerOffset + r2 + u; 234 | var d = cornerOffset + r2 + u1; 235 | 236 | if( !flips[i] ){ 237 | 238 | indices.push( a ); 239 | indices.push( b ); 240 | indices.push( c ); 241 | 242 | indices.push( b ); 243 | indices.push( d ); 244 | indices.push( c ); 245 | 246 | } else { 247 | 248 | indices.push( a ); 249 | indices.push( c ); 250 | indices.push( b ); 251 | 252 | indices.push( b ); 253 | indices.push( c ); 254 | indices.push( d ); 255 | 256 | } 257 | 258 | } 259 | 260 | } 261 | 262 | for ( var u = 0 ; u < radiusSegments ; u ++ ){ 263 | 264 | var a = cornerOffset + lastRowOffset + u; 265 | var b = cornerOffset + lastRowOffset + u + 1; 266 | var c = cornerOffset + lastVertex; 267 | 268 | if( !flips[i] ){ 269 | 270 | indices.push( a ); 271 | indices.push( b ); 272 | indices.push( c ); 273 | 274 | } else { 275 | 276 | indices.push( a ); 277 | indices.push( c ); 278 | indices.push( b ); 279 | 280 | } 281 | 282 | } 283 | 284 | } 285 | 286 | } 287 | 288 | 289 | //plates ============================================ 290 | //fix this loop matrices find pattern something 291 | 292 | function doFaces(){ 293 | 294 | //top 295 | var a = lastVertex;// + cornerVertNumber * 0; 296 | var b = lastVertex + cornerVertNumber;// * 1; 297 | var c = lastVertex + cornerVertNumber * 2; 298 | var d = lastVertex + cornerVertNumber * 3; 299 | 300 | indices.push( a ); 301 | indices.push( b ); 302 | indices.push( c ); 303 | indices.push( a ); 304 | indices.push( c ); 305 | indices.push( d ); 306 | 307 | //bottom 308 | a = lastVertex + cornerVertNumber * 4;// + cornerVertNumber * 0; 309 | b = lastVertex + cornerVertNumber * 5;// * 1; 310 | c = lastVertex + cornerVertNumber * 6; 311 | d = lastVertex + cornerVertNumber * 7; 312 | 313 | indices.push( a ); 314 | indices.push( c ); 315 | indices.push( b ); 316 | indices.push( a ); 317 | indices.push( d ); 318 | indices.push( c ); 319 | 320 | //left 321 | a = 0; 322 | b = cornerVertNumber; 323 | c = cornerVertNumber * 4; 324 | d = cornerVertNumber * 5; 325 | 326 | indices.push( a ); 327 | indices.push( c ); 328 | indices.push( b ); 329 | indices.push( b ); 330 | indices.push( c ); 331 | indices.push( d ); 332 | 333 | //right 334 | a = cornerVertNumber * 2; 335 | b = cornerVertNumber * 3; 336 | c = cornerVertNumber * 6; 337 | d = cornerVertNumber * 7; 338 | 339 | indices.push( a ); 340 | indices.push( c ); 341 | indices.push( b ); 342 | indices.push( b ); 343 | indices.push( c ); 344 | indices.push( d ); 345 | 346 | //front 347 | a = radiusSegments; 348 | b = radiusSegments + cornerVertNumber * 3; 349 | c = radiusSegments + cornerVertNumber * 4; 350 | d = radiusSegments + cornerVertNumber * 7; 351 | 352 | indices.push( a ); 353 | indices.push( b ); 354 | indices.push( c ); 355 | indices.push( b ); 356 | indices.push( d ); 357 | indices.push( c ); 358 | 359 | //back 360 | a = radiusSegments + cornerVertNumber; 361 | b = radiusSegments + cornerVertNumber * 2; 362 | c = radiusSegments + cornerVertNumber * 5; 363 | d = radiusSegments + cornerVertNumber * 6; 364 | 365 | indices.push( a ); 366 | indices.push( c ); 367 | indices.push( b ); 368 | indices.push( b ); 369 | indices.push( c ); 370 | indices.push( d ); 371 | 372 | } 373 | 374 | 375 | // weave edges ====================================== 376 | 377 | function doHeightEdges(){ 378 | 379 | for ( var i = 0 ; i < 4 ; i ++ ){ 380 | 381 | var cOffset = i * cornerVertNumber; 382 | var cRowOffset = 4 * cornerVertNumber + cOffset; 383 | var needsFlip = i & 1 === 1; 384 | for ( var u = 0 ; u < radiusSegments ; u ++ ){ 385 | 386 | var u1 = u + 1; 387 | var a = cOffset + u; 388 | var b = cOffset + u1; 389 | var c = cRowOffset + u; 390 | var d = cRowOffset + u1; 391 | 392 | if( !needsFlip ){ 393 | 394 | indices.push( a ); 395 | indices.push( b ); 396 | indices.push( c ); 397 | indices.push( b ); 398 | indices.push( d ); 399 | indices.push( c ); 400 | 401 | } else { 402 | 403 | indices.push( a ); 404 | indices.push( c ); 405 | indices.push( b ); 406 | indices.push( b ); 407 | indices.push( c ); 408 | indices.push( d ); 409 | 410 | } 411 | 412 | } 413 | 414 | } 415 | 416 | } 417 | 418 | function doDepthEdges(){ 419 | 420 | var cStarts = [ 0 , 2 , 4 , 6 ]; 421 | var cEnds = [ 1 , 3 , 5 , 7 ]; 422 | 423 | for ( var i = 0 ; i < 4 ; i ++ ){ 424 | 425 | var cStart = cornerVertNumber * cStarts[ i ]; 426 | var cEnd = cornerVertNumber * cEnds[ i ]; 427 | 428 | var needsFlip = 1 >= i; 429 | 430 | for ( var u = 0 ; u < radiusSegments ; u ++ ){ 431 | 432 | var urs1 = u * rs1; 433 | var u1rs1 = (u+1) * rs1; 434 | 435 | var a = cStart + urs1; 436 | var b = cStart + u1rs1; 437 | var c = cEnd + urs1; 438 | var d = cEnd + u1rs1 439 | 440 | if( needsFlip ){ 441 | 442 | indices.push( a ); 443 | indices.push( c ); 444 | indices.push( b ); 445 | indices.push( b ); 446 | indices.push( c ); 447 | indices.push( d ); 448 | 449 | } else { 450 | 451 | indices.push( a ); 452 | indices.push( b ); 453 | indices.push( c ); 454 | indices.push( b ); 455 | indices.push( d ); 456 | indices.push( c ); 457 | 458 | } 459 | 460 | } 461 | 462 | } 463 | 464 | } 465 | 466 | function doWidthEdges(){ 467 | 468 | var end = radiusSegments - 1; 469 | 470 | var cStarts = [ 0 , 1 , 4 , 5 ]; 471 | var cEnds = [ 3 , 2 , 7 , 6 ]; 472 | var needsFlip = [0,1,1,0]; 473 | 474 | for ( var i = 0 ; i < 4 ; i ++ ){ 475 | 476 | var cStart = cStarts[i] * cornerVertNumber; 477 | var cEnd = cEnds[i] * cornerVertNumber; 478 | 479 | 480 | for ( var u = 0 ; u <= end ; u ++ ){ 481 | 482 | // var dInd = u != end ? radiusSegments + u * rs1 : cornerVertNumber - 1; 483 | 484 | var a = cStart + radiusSegments + u * rs1; 485 | var b = cStart + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1); 486 | 487 | var c = cEnd + radiusSegments + u * rs1; 488 | var d = cEnd + (u != end ? radiusSegments + (u + 1) * rs1 : cornerVertNumber - 1); 489 | 490 | if( !needsFlip[i] ){ 491 | 492 | indices.push( a ); 493 | indices.push( b ); 494 | indices.push( c ); 495 | indices.push( b ); 496 | indices.push( d ); 497 | indices.push( c ); 498 | 499 | } 500 | else { 501 | 502 | indices.push( a ); 503 | indices.push( c ); 504 | indices.push( b ); 505 | indices.push( b ); 506 | indices.push( c ); 507 | indices.push( d ); 508 | 509 | } 510 | 511 | } 512 | 513 | } 514 | 515 | } 516 | 517 | 518 | //fill buffers ====================================== 519 | 520 | var index = 0; 521 | 522 | for ( var i = 0 ; i < vertexPool.length ; i ++ ){ 523 | 524 | positions.setXYZ( 525 | index , 526 | vertexPool[i].x , 527 | vertexPool[i].y , 528 | vertexPool[i].z 529 | ); 530 | 531 | normals.setXYZ( 532 | index , 533 | normalPool[i].x , 534 | normalPool[i].y , 535 | normalPool[i].z 536 | ); 537 | 538 | index++; 539 | 540 | } 541 | 542 | this.setIndex( new THREE.BufferAttribute( new Uint16Array( indices ) , 1 ) ); 543 | 544 | this.setAttribute( 'position', positions ); 545 | 546 | this.setAttribute( 'normal', normals ); 547 | 548 | }; 549 | 550 | RoundedBoxGeometry.prototype = Object.create( THREE.BufferGeometry.prototype ); 551 | RoundedBoxGeometry.constructor = RoundedBoxGeometry; 552 | 553 | return RoundedBoxGeometry; 554 | 555 | } 556 | --------------------------------------------------------------------------------