diff --git a/src/assets-utils.js b/src/assets-utils.js index fb50959..cda881b 100644 --- a/src/assets-utils.js +++ b/src/assets-utils.js @@ -282,6 +282,30 @@ class RoundedBoxGeometry extends BufferGeometry { } + static getRectangles( x, y, z, faces = [ 1, 1, 1, 1, 1, 1 ]) { + + let perm; + if ( x <= y && x <= z ) perm = [ 2, 0, 1 ]; + else if ( y <= x && y <= z ) perm = [ 0, 1, 2 ]; + else if ( z <= x && z <= y ) perm = [ 1, 2, 0 ]; + + let size = [ x, y, z ]; + size = [ size[ perm[ 0 ] ], size[ perm[ 1 ] ], size[ perm[ 2 ] ] ]; + x = size[ 0 ]; + y = size[ 1 ]; + z = size[ 2 ]; + let res =[]; + + res.push( { width: x, height: y } ); + res.push( { width: x, height: y } ); + res.push( { width: y, height: z } ); + res.push( { width: y, height: z } ); + res.push( { width: z, height: x } ); + res.push( { width: z, height: x } ); + return res; + + } + constructor( x, y, z, segments = 2, roundness = 0, faces = [ 1, 1, 1, 1, 1, 1 ], @@ -347,16 +371,34 @@ class RoundedBoxGeometry extends BufferGeometry { const uvRemapMatrix = ( tx, ty, sx, sy, r = 0 ) => new Matrix3().scale( sx, sy ).rotate( r / 180 * Math.PI ).translate( tx, ty ); - let m = [ - uvRemapMatrix( y+x, 0, -x, y ).premultiply( uvMatrix ), - uvRemapMatrix( y+x, y+z+y, -x, -y ).premultiply( uvMatrix ), + let m; + if ( Array.isArray( uvMatrix ) ) { - uvRemapMatrix( y+x+y, y, -y, z ).premultiply( uvMatrix ), - uvRemapMatrix( 0, y, y, z ).premultiply( uvMatrix ), + m = [ + uvRemapMatrix( x, 0, -x, y ).premultiply( uvMatrix[ 0 ]), + uvRemapMatrix( x, y, -x, -y ).premultiply( uvMatrix[ 1 ]), - uvRemapMatrix( 2*y + x, y, -z, x, 90 ).premultiply( uvMatrix ), - uvRemapMatrix( y + x, y, -z, -x, 90 ).premultiply( uvMatrix ), - ]; + uvRemapMatrix( y, 0, -y, z ).premultiply( uvMatrix[ 2 ]), + uvRemapMatrix( 0, 0, y, z ).premultiply( uvMatrix[ 3 ]), + + uvRemapMatrix( 0, 0, z, x ).premultiply( uvMatrix[ 4 ]), + uvRemapMatrix( 0, 0, z, x ).premultiply( uvMatrix[ 5 ]), + ]; + + } else { + + m = [ + uvRemapMatrix( y+x, 0, -x, y ).premultiply( uvMatrix ), + uvRemapMatrix( y+x, y+z+y, -x, -y ).premultiply( uvMatrix ), + + uvRemapMatrix( y+x+y, y, -y, z ).premultiply( uvMatrix ), + uvRemapMatrix( 0, y, y, z ).premultiply( uvMatrix ), + + uvRemapMatrix( 2*y + x, y, -z, x, 90 ).premultiply( uvMatrix ), + uvRemapMatrix( y + x, y, -z, -x, 90 ).premultiply( uvMatrix ), + ]; + + } // count offset for writing let vertexOffset = 0; diff --git a/src/bin-packing.js b/src/bin-packing.js new file mode 100644 index 0000000..dd65e6b --- /dev/null +++ b/src/bin-packing.js @@ -0,0 +1,384 @@ + +import { Vector2 } from 'three'; + + +//______________________________________________________________________________ +// Rect class +function Rect( x, y, width, height ) { + + this.x = x; + this.y = y; + this.width = width; + this.height = height; + +} + +Rect.prototype.contains = function ( r ) { + + // Does this rectangle contain the specified rectangle? + return this.x <= r.x && + this.y <= r.y && + this.x + this.width >= r.x + r.width && + this.y + this.height >= r.y + r.height; + +}; + +Rect.prototype.disjointFrom = function ( r ) { + + // Is this rectangle disjoint from the specified rectangle? + return this.x + this.width <= r.x || + this.y + this.height <= r.y || + r.x + r.width <= this.x || + r.y + r.height <= this.y; + +}; + +Rect.prototype.intersects = function ( r ) { + + // Does this rectangle intersect the specified rectangle? + return !this.disjointFrom( r ); + +}; + +Rect.prototype.copy = function () { + + // Create a copy of this rectangle. + return new Rect( this.x, this.y, this.width, this.height ); + +}; + +//______________________________________________________________________________ +// BinPacker class + +// Uses MAXRECTS-BSSF-BNF bin packer algorithm from +// https://github.com/juj/RectangleBinPack +// +// MAXRECTS-BSSF-BNF stands for "Maximal Rectangles - Best Short Side Fit". It +// positions the rectangle against the short side of the free rectangle into +// which it fits most snugly. + +function BinPacker( width, height ) { + + this.width = width; + this.height = height; + + // TODO: Allow for flexible width or height. If a rectangle doesn't fit into + // the bin extend the width or height to accommodate it. + + // Array of rectangles representing the free space in the bin + this.freeRectangles = [ new Rect( 0, 0, width, height ) ]; + + // Array of rectangles positioned in the bin + this.positionedRectangles = []; + + // Array of rectangles that couldn't fit in the bin + this.unpositionedRectangles = []; + +} + +BinPacker.prototype.insert = function ( width, height ) { + + // Insert a rectangle into the bin. + // + // If the rectangle was successfully positioned, add it to the array of + // positioned rectangles and return an object with this information and the + // rectangle object. + // + // If the rectangle couldn't be positioned in the bin, add it to the array of + // unpositioned rectangles and return an object with this information and the + // rectangle object (which as undefined x- and y-properties. + + // Find where to put the rectangle. Searches the array of free rectangles for + // an open spot and returns one when it's found. + var r = BinPacker.findPosition( width, height, this.freeRectangles ); + + // Unpositioned rectangle (it has no x-property if it's unpositioned) + if ( r.x == undefined ) { + + this.unpositionedRectangles.push( r ); + return { positioned: false, rectangle: r }; + + } + + // Split the free rectangles based on where the new rectangle is positioned + var n = this.freeRectangles.length; + for ( var i = 0; i < n; i++ ) { + + let new_rectangles; + // splitRectangle() returns an array of sub-rectangles if the rectangle + // was split (which is truthy) and false otherwise + if ( new_rectangles = BinPacker.splitRectangle( this.freeRectangles[ i ], r ) ) { + + // remove the free rectangle that was split + this.freeRectangles.splice( i, 1 ); + + // append new free rectangles formed by the split // split + this.freeRectangles = this.freeRectangles.concat( new_rectangles ); + + --i; --n; + + } + + } + + BinPacker.pruneRectangles( this.freeRectangles ); + + this.positionedRectangles.push( r ); + + return { positioned: true, rectangle: r }; + +}; + +BinPacker.findPosition = function ( width, height, F ) { + + // Decide where to position a rectangle (with side lengths specified by width + // and height) within the bin. The bin's free space is defined in the array + // of free rectangles, F. + + var bestRectangle = new Rect( undefined, undefined, width, height ); + + var bestShortSideFit = Number.MAX_VALUE, + bestLongSideFit = Number.MAX_VALUE; + + // Find the free rectangle into which this rectangle fits inside most snugly + // (i.e., the one with the smallest amount of space leftover after positioning + // the rectangle inside of it) + for ( var i = 0; i < F.length; i++ ) { + + var f = F[ i ]; // the current free rectangle + + // Does the rectangle we are positioning fit inside the free rectangle? + if ( f.width >= width && f.height >= height ) { + + var leftoverHorizontal = Math.abs( f.width - width ), + leftoverVertical = Math.abs( f.height - height ); + + var shortSideFit = Math.min( leftoverHorizontal, leftoverVertical ), + longSideFit = Math.max( leftoverHorizontal, leftoverVertical ); + + // Does this free rectangle have the smallest amount of space leftover + // after positioning? + if ( shortSideFit < bestShortSideFit || + ( shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit ) ) { + + // Position rectangle in the bottom-left corner of the free rectangle + // (or top-left if the y-axis is inverted like in browsers) + bestRectangle.x = f.x; + bestRectangle.y = f.y; + + bestShortSideFit = shortSideFit; + bestLongSideFit = longSideFit; + + } + + } + + } + + return bestRectangle; + +}; + +BinPacker.splitRectangle = function ( f, r ) { + + // Splits the rectangle f into at most four sub-rectangles that are formed by + // taking the geometric difference of f from r and identifying the largest + // rectangles that can be formed from the resulting polygon. Returns these + // sub-rectangles if the f was split and false otherwise. + + // If they are disjoint then no splitting can be done, return false + if ( r.disjointFrom( f ) ) return false; + + var new_rectangles = []; + + // Does f contain r in terms of the x-axis? + if ( r.x < f.x + f.width && f.x < r.x + r.width ) { + + // QUESTION: Does this make an assumption about how r is positioned relative + // to f? Couldn't it be that part of r could be outside of f in + // this first if-statement? It looks like this assumes r will be + // placed along one of the edges (which, in fact, is what this + // algorithm does). + + // TODO: Look into all of this in more depth. I don't fully understand why + // these conditionals are the way they are. + + // New rectangle is above r + if ( f.y < r.y && r.y < f.y + f.height ) { + + var new_rectangle = f.copy(); + new_rectangle.height = r.y - new_rectangle.y; + new_rectangles.push( new_rectangle ); + + } + + // New rectangle is below r + if ( r.y + r.height < f.y + f.height ) { + + var new_rectangle = f.copy(); + new_rectangle.y = r.y + r.height; + new_rectangle.height = f.y + f.height - ( r.y + r.height ); + new_rectangles.push( new_rectangle ); + + } + + } + + // Does f contain r in terms of the y-axis? + if ( r.y < f.y + f.height && f.y < r.y + r.height ) { + + // New rectangle is to the left of r + if ( f.x < r.x && r.x < f.x + f.width ) { + + var new_rectangle = f.copy(); + new_rectangle.width = r.x - new_rectangle.x; + new_rectangles.push( new_rectangle ); + + } + + // New rectangle is to the right of r + if ( r.x + r.width < f.x + f.width ) { + + var new_rectangle = f.copy(); + new_rectangle.x = r.x + r.width; + new_rectangle.width = f.x + f.width - ( r.x + r.width ); + new_rectangles.push( new_rectangle ); + + } + + } + + return new_rectangles; + +}; + +BinPacker.pruneRectangles = function ( F ) { + + // Go through the array of rectangles, F, and remove any that are + // completely contained within another rectangle in F + + for ( var i = 0; i < F.length; i++ ) { + + for ( var j = i + 1; j < F.length; j++ ) { + + if ( F[ j ].contains( F[ i ]) ) { + + F.splice( i, 1 ); + --i; + break; + + } + + if ( F[ i ].contains( F[ j ]) ) { + + F.splice( j, 1 ); + --j; + + } + + } + + } + +}; + +function BinPack() { + + var binWidth = 800, + binHeight = 800; + + var rectWidth = function ( d ) { + + return d.width; + + }, + rectHeight = function ( d ) { + + return d.height; + + }; + + var sort = false; + + var binPacker = new BinPacker( binWidth, binHeight ); + + var pack = {}; + + pack.add = function ( d ) { + + var o = binPacker.insert( rectWidth( d ), rectHeight( d ) ); + o.rectangle.datum = d; + return pack; + + }; + + pack.addAll = function ( array ) { + + if ( sort ) array.sort( sort ); + array.forEach( function ( d, i ) { + + var o = binPacker.insert( rectWidth( d ), rectHeight( d ) ); + o.rectangle.datum = d; + + } ); + return pack; + + }; + + pack.binWidth = function ( _ ) { + + if ( !arguments.length ) return binWidth; + binWidth = _; + binPacker = new BinPacker( binWidth, binHeight ); + return pack; + + }; + + pack.binHeight = function ( _ ) { + + if ( !arguments.length ) return binHeight; + binHeight = _; + binPacker = new BinPacker( binWidth, binHeight ); + return pack; + + }; + + pack.rectWidth = function ( _ ) { + + return arguments.length ? ( rectWidth = _, pack ) : rectWidth; + + }; + + pack.rectHeight = function ( _ ) { + + return arguments.length ? ( rectHeight = _, pack ) : rectHeight; + + }; + + pack.sort = function ( _ ) { + + return arguments.length ? ( sort = _, pack ) : sort; + + }; + + Object.defineProperty( pack, "positioned", { + get: function () { + + return binPacker.positionedRectangles; + + } + } ); + + Object.defineProperty( pack, "unpositioned", { + get: function () { + + return binPacker.unpositionedRectangles; + + } + } ); + + return pack; + +} + +export { BinPack }; diff --git a/src/chair.js b/src/chair.js index ed4778f..e7da28b 100644 --- a/src/chair.js +++ b/src/chair.js @@ -1,6 +1,7 @@ import * as THREE from 'three'; import * as ASSETS from './assets-utils.js'; import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js'; +import * as BP from './bin-packing.js'; class Chair extends ASSETS.Asset { @@ -9,25 +10,25 @@ class Chair extends ASSETS.Asset { /* eslint-disable */ static paramData = { - seatWidth: { default: 42, type: 'cm' , min: 20, max: 100, prec: 1, folder: "Seat" , name: "Width" }, - seatDepth: { default: 45, type: 'cm' , min: 20, max: 100, prec: 1, folder: "Seat" , name: "Depth" }, - seatHeight: { default: 45, type: 'cm' , min: 20, max: 100, prec: 1, folder: "Seat" , name: "Height" }, - seatThickness: { default: 2, type: 'cm' , min: 2, max: 10 , prec: 1, folder: "Seat" , name: "Thickness" }, - - backrestHeight: { default: 50, type: 'cm' , min: 10, max: 100, prec: 1, folder: "Backrest" , name: "Height" }, - backrestSidesThickness: { default: 4, type: 'cm' , min: 1, max: 10 , prec: 1, folder: "Backrest" , name: "Thickness" }, - backrestAngle: { default: 5, type: 'deg' , min: 0, max: 45 , prec: 1, folder: "Backrest" , name: "Angle" }, - - cussionThickness: { default: 3, type: 'cm' , min: 2, max: 15 , prec: 1, folder: "Cussions" , name: "Thickness" }, - cussionOffset: { default: 2, type: 'cm' , min: 2, max: 5 , prec: 1, folder: "Cussions" , name: "Offset" }, - cussionRoundness: { default: 0.2, type: Number , min: 0, max: 0.2, prec: 2, folder: "Cussions" , name: "Roundness" }, - upholstery: { default: false, type: Boolean, chance: .5 , folder: "Cussions" , name: "Upholstery" }, - - legThickness: { default: 4, type: 'cm' , min: 2, max: 10 , prec: 1, folder: "Legs" , name: "Thickness" }, - - cussionDetail: { default: 2, type: 'n' , min: 1, max: 10 , prec: 0, folder: "Complexity", name: "Cussions" , exp: true }, - flat: { default: false, type: Boolean, chance: .3 , folder: "Complexity", name: "Simple" }, - simple: { default: false, type: Boolean, chance: .3 , folder: "Complexity", name: "Flat" }, + seatWidth: {default: 42, type: 'cm', min: 20, max: 100, prec: 1, folder: "Seat", name: "Width"}, + seatDepth: {default: 45, type: 'cm', min: 20, max: 100, prec: 1, folder: "Seat", name: "Depth"}, + seatHeight: {default: 45, type: 'cm', min: 20, max: 100, prec: 1, folder: "Seat", name: "Height"}, + seatThickness: {default: 2, type: 'cm', min: 2, max: 10, prec: 1, folder: "Seat", name: "Thickness"}, + + backrestHeight: {default: 50, type: 'cm', min: 10, max: 100, prec: 1, folder: "Backrest", name: "Height"}, + backrestSidesThickness: {default: 4, type: 'cm', min: 1, max: 10, prec: 1, folder: "Backrest", name: "Thickness"}, + backrestAngle: {default: 5, type: 'deg', min: 0, max: 45, prec: 1, folder: "Backrest", name: "Angle"}, + + cussionThickness: {default: 3, type: 'cm', min: 2, max: 15, prec: 1, folder: "Cussions", name: "Thickness"}, + cussionOffset: {default: 2, type: 'cm', min: 2, max: 5, prec: 1, folder: "Cussions", name: "Offset"}, + cussionRoundness: {default: 0.2, type: Number, min: 0, max: 0.2, prec: 2, folder: "Cussions", name: "Roundness"}, + upholstery: {default: false, type: Boolean, chance: .5, folder: "Cussions", name: "Upholstery"}, + + legThickness: {default: 4, type: 'cm', min: 2, max: 10, prec: 1, folder: "Legs", name: "Thickness"}, + + cussionDetail: {default: 2, type: 'n', min: 1, max: 10, prec: 0, folder: "Complexity", name: "Cussions", exp: true}, + flat: {default: false, type: Boolean, chance: .3, folder: "Complexity", name: "Simple"}, + simple: {default: false, type: Boolean, chance: .3, folder: "Complexity", name: "Flat"}, }; /* eslint-enable */ @@ -71,43 +72,68 @@ class Chair extends ASSETS.Asset { material.flatShading = params.flat; - const uvRemap = ( tx = 0, ty = 0, s = 1, r = 0 ) => { + const l = []; + l.push( ...ASSETS.RoundedBoxGeometry.getRectangles( seatWidth, seatThickness, seatDepth ) ); // seat - return new THREE.Matrix3().rotate( r / 180 * Math.PI ).translate( tx, ty ).scale( s, s ); + l.push( ...ASSETS.RoundedBoxGeometry.getRectangles( + seatCussionWidth + ( upholstery ? r1 / Math.sqrt( 3 ) : 0 ), + upholstery ? 1. : cussionThickness, + seatCussionDepth + ( upholstery ? r1 / Math.sqrt( 3 ) : 0 ) + ) ); // cussion1 - }; + l.push( ...ASSETS.RoundedBoxGeometry.getRectangles( + legThickness, seatHeight - seatThickness, legThickness, [ 1, 1, 1, 1, 1, 0 ]) ); // legs - const uvEmpty = 0.03; - const uvX1 = 2*uvEmpty + legThickness * 4; - const uvY1 = 2*uvEmpty + seatDepth + seatThickness * 2; + l.push( ...ASSETS.RoundedBoxGeometry.getRectangles( + backrestSidesThickness, backrestHeight, backrestSidesThickness, [ 1, 1, 1, 1, 0, 1 ]) ); // backrest - const uvY2 = 2 * uvEmpty + 2 * cussionThickness + backrestCussionWidth; + l.push( ...ASSETS.RoundedBoxGeometry.getRectangles( + ( upholstery ? seatWidth : backrestCussionWidth ) + ( upholstery ? r2 / Math.sqrt( 3 ) : 0 ), + backrestHeight + ( upholstery ? r2 / Math.sqrt( 3 ) : 0 ), + upholstery ? 1 : cussionThickness, + [ !upholstery, 1, !upholstery, !upholstery, 0, !upholstery ]) ); // cussion2 + l.forEach( ( rect ) => { - const uvBoundX0 = 2*uvEmpty + 2 * seatWidth + 2 * seatThickness; - const uvBoundY0 = - uvEmpty + uvY1 + Math.max( backrestHeight + backrestSidesThickness, seatHeight + legThickness ); + rect.width += 0.01; + rect.height += 0.01; - const uvBoundX1 = - 2 * uvEmpty + - Math.max( 2 * backrestHeight + 2 * cussionThickness, 2 * seatCussionWidth + 2 * cussionThickness ); - const uvBoundY1 = 2 * uvEmpty + backrestCussionWidth + seatCussionDepth + 4 * cussionThickness; + } ); - const uvScale0 = 1 / Math.max( uvBoundX0, uvBoundY0 ); - const uvScale1 = 1 / Math.max( uvBoundX1, uvBoundY1 ); + //console.log( "packing", l.length, "rectangles" ); + console.time( 'test' ); + let repeat = true; + let scale = 1; + let binPacker; + while ( repeat ) { - const uvMatrices = [ - uvRemap( uvEmpty, uvEmpty, uvScale0 ), // seat - uvRemap( uvEmpty, uvY2, uvScale1 ), // cussion1 - uvRemap( uvEmpty, uvY1, uvScale0 ), // legs - uvRemap( uvX1, uvY1 - backrestSidesThickness, uvScale0 ), // backrestL - uvRemap( uvX1 + backrestSidesThickness * 4, uvY1 - backrestSidesThickness, uvScale0 ), // backrestR - uvRemap( uvEmpty, uvEmpty, uvScale1 ), // cussion2 - ]; + binPacker = BP.BinPack(); + binPacker.binWidth( scale ); + binPacker.binHeight( scale ); + binPacker.addAll( l ); + if ( binPacker.unpositioned.length == 0 ) repeat = false; + else scale *= 1.1; + + } + + console.timeEnd( 'test' ); + //console.log( "packed", binPacker.positioned.length, "failed", binPacker.unpositioned.length, 'scale', scale ); + + const uvRemap = ( tx = 0, ty = 0, s = 1, r = 0 ) => { + + return new THREE.Matrix3().rotate( r / 180 * Math.PI ).translate( tx, ty ).scale( s, s ); + + }; + + const pack = binPacker.positioned; + //console.log( pack ); + + const uvMatrices = pack.map( ( rect ) => uvRemap( rect.x + 0.005, rect.y + 0.005, 1./scale, 0 ) ); + //console.log( uvMatrices ); const seat = new ASSETS.RoundedBoxGeometry( - seatWidth, seatThickness, seatDepth, + seatWidth, seatThickness, seatDepth, undefined, undefined, undefined, - uvMatrices[ 0 ] + uvMatrices.slice( 0, 6 ) ).translate( 0, seatHeight - seatThickness / 2, 0 ); @@ -123,12 +149,12 @@ class Chair extends ASSETS.Asset { simple ? undefined : cussionDetail, simple ? undefined : cussionRoundness, [ !upholstery, !upholstery, !upholstery, !upholstery, 0, 1 ], - uvMatrices[ 1 ] + uvMatrices.slice( 6, 12 ) ); if ( upholstery ) cussion1.translate( 0, - seatHeight - 0.5 + r1 * ( 1 - 1/Math.sqrt( 2 ) ), + seatHeight - 0.5 + r1 * ( 1 - 1 / Math.sqrt( 2 ) ), -cussionOffset / 4, ); else @@ -152,7 +178,7 @@ class Chair extends ASSETS.Asset { legThickness, seatHeight - seatThickness, legThickness, undefined, undefined, [ 1, 1, 1, 1, 1, 0 ], - uvMatrices[ 2 ] + uvMatrices.slice( 12, 18 ) ).translate( legPositions[ i ].x, seatHeight / 2 - seatThickness / 2, legPositions[ i ].y ); @@ -161,13 +187,13 @@ class Chair extends ASSETS.Asset { // backrest const backrestMatrix = - new THREE.Matrix4().makeTranslation( - 0, - seatHeight - Math.sin( backrestAngle ) * backrestSidesThickness / 2, - -seatDepth / 2 + backrestSidesThickness / 2 - ).multiply( - new THREE.Matrix4().makeRotationX( -backrestAngle ) - ); + new THREE.Matrix4().makeTranslation( + 0, + seatHeight - Math.sin( backrestAngle ) * backrestSidesThickness / 2, + -seatDepth / 2 + backrestSidesThickness / 2 + ).multiply( + new THREE.Matrix4().makeRotationX( -backrestAngle ) + ); let backrestSideL = null; let backrestSideR = null; @@ -178,7 +204,7 @@ class Chair extends ASSETS.Asset { seatWidth - 0.001, backrestHeight, backrestSidesThickness, undefined, undefined, [ 1, 1, 1, 1, 0, 1 ], - uvMatrices[ 3 ] + uvMatrices.slice( 18, 24 ) ).translate( 0, backrestHeight / 2, 0 ).applyMatrix4( backrestMatrix ); } else { @@ -188,7 +214,7 @@ class Chair extends ASSETS.Asset { backrestScale.x, backrestScale.y, backrestScale.z, undefined, undefined, [ 1, 1, 1, 1, 0, 1 ], - uvMatrices[ 3 ] + uvMatrices.slice( 18, 24 ) ).translate( seatWidth / 2 - backrestSidesThickness / 2 - 0.001, backrestHeight / 2, 0 ).applyMatrix4( backrestMatrix ); @@ -197,7 +223,7 @@ class Chair extends ASSETS.Asset { backrestScale.x, backrestScale.y, backrestScale.z, undefined, undefined, [ 1, 1, 1, 1, 0, 1 ], - uvMatrices[ 4 ] + uvMatrices.slice( 18, 24 ) ).translate( -seatWidth / 2 + backrestSidesThickness / 2 + 0.001, backrestHeight / 2, 0 ).applyMatrix4( backrestMatrix ); @@ -217,13 +243,13 @@ class Chair extends ASSETS.Asset { simple ? undefined : cussionDetail, simple ? undefined : cussionRoundness, [ !upholstery, 1, !upholstery, !upholstery, 0, !upholstery ], - uvMatrices[ 5 ] + uvMatrices.slice( 24, 30 ) ); if ( upholstery ) cussion2.translate( 0, backrestHeight / 2, - -.5 + backrestSidesThickness / 2 + r2 * ( 1 - 1/Math.sqrt( 2 ) ) + -.5 + backrestSidesThickness / 2 + r2 * ( 1 - 1 / Math.sqrt( 2 ) ) ).applyMatrix4( backrestMatrix ); else cussion2.translate( @@ -247,7 +273,7 @@ class Chair extends ASSETS.Asset { cussion1, cussion2 ]); - this.cussions.uvIndex = 1; + this.cussions.uvIndex = 0; } @@ -262,7 +288,7 @@ class Chair extends ASSETS.Asset { } - this.position.y = -( seatHeight+backrestHeight )/2; + this.position.y = -( seatHeight + backrestHeight ) / 2; seat.dispose(); backrestSideL.dispose();