three-rwx-loader
Version:
three.js RWX (AW) loader
595 lines (414 loc) • 19.4 kB
JavaScript
/**
* @author Julien 'Blaxar' Bardagi <blaxar.waldarax@gmail.com>
*/
import { LinearSRGBColorSpace, SRGBColorSpace, TextureLoader, Mesh, Group, Box3, Raycaster, Vector3, MeshPhongMaterial } from 'three';
import RWXLoader, { RWXMaterial, RWXMaterialManager, RWXMaterialTracker, LightSampling, GeometrySampling,
TextureMode, MaterialMode, TextureAddressMode, scaleGroupName, firstClumpName } from './RWXLoader.js';
import * as fflate from 'fflate';
import { createServer } from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import * as assert from 'assert';
const port = 8085;
const epsilon = 0.00001;
const isCloseTo = ( val, ref, eps ) => {
return val < ref + eps && val > ref - eps ;
};
const loadTestCube = ( flatten ) => {
return new Promise( ( resolve, error ) => {
const loader = new RWXLoader();
assert.strictEqual( loader.setPath( 'http://127.0.0.1:' + port + '/samples/rwx' )
.setResourcePath( 'http://127.0.0.1:' + port + '/samples/textures' ).setEnableTextures( false )
.setWaitFullLoad( true ).setFlatten( flatten ), loader );
loader.load( 'cube.rwx', rwx => {
resolve( rwx );
}, null, e => {
error( e );
} );
} );
};
const probeTestCube = ( node, groupCb, meshCb ) => {
for ( const child of node.children ) {
if ( child instanceof Group ) {
groupCb( child );
probeTestCube( child, groupCb, meshCb );
}
if ( child instanceof Mesh ) {
meshCb( child );
}
}
};
describe( 'RWXLoader', () => {
it( 'setFflate', () => {
let loader = new RWXLoader();
assert.equal( loader.fflate, null );
assert.strictEqual( loader.setFflate( fflate ), loader );
assert.strictEqual( loader.fflate, fflate );
} );
it( 'setTextureExtension', () => {
let loader = new RWXLoader();
assert.equal( loader.textureExtension, '.jpg' );
assert.strictEqual( loader.setTextureExtension( '.png' ), loader );
assert.equal( loader.textureExtension, '.png' );
} );
it( 'setMaskExtension', () => {
let loader = new RWXLoader();
assert.equal( loader.maskExtension, '.zip' );
assert.strictEqual( loader.setMaskExtension( '.bmp' ), loader );
assert.equal( loader.maskExtension, '.bmp' );
} );
it( 'setWaitFullLoad', () => {
let loader = new RWXLoader();
assert.equal( loader.waitFullLoad, false );
assert.strictEqual( loader.setWaitFullLoad( true ), loader );
assert.equal( loader.waitFullLoad, true );
} );
it( 'setFlatten', () => {
let loader = new RWXLoader();
assert.equal( loader.flatten, false );
assert.strictEqual( loader.setFlatten( true ), loader );
assert.equal( loader.flatten, true );
} );
it( 'setRWXMaterialManager', () => {
let loader = new RWXLoader();
let mgr = new RWXMaterialManager();
assert.equal( loader.rwxMaterialManager, null );
assert.strictEqual( loader.setRWXMaterialManager( mgr ), loader );
assert.strictEqual( loader.rwxMaterialManager, mgr );
} );
it( 'setUseBasicMaterial', () => {
let loader = new RWXLoader();
assert.equal( loader.useBasicMaterial, false );
assert.strictEqual( loader.setUseBasicMaterial( true ), loader );
assert.equal( loader.useBasicMaterial, true );
} );
it( 'setTextureColorSpace', () => {
let loader = new RWXLoader();
assert.equal( loader.textureColorSpace, SRGBColorSpace );
assert.strictEqual( loader.setTextureColorSpace( LinearSRGBColorSpace ), loader );
assert.equal( loader.textureColorSpace, LinearSRGBColorSpace );
} );
it( 'setEnableTextures', () => {
let loader = new RWXLoader();
assert.equal( loader.enableTextures, true );
assert.strictEqual( loader.setEnableTextures( false ), loader );
assert.equal( loader.enableTextures, false );
} );
it( 'setForceEarcut', () => {
let loader = new RWXLoader();
assert.equal( loader.forceEarcut, false );
assert.strictEqual( loader.setForceEarcut( true ), loader );
assert.equal( loader.forceEarcut, true );
} );
it( 'setVerboseWarning', () => {
let loader = new RWXLoader();
assert.equal( loader.verboseWarning, false );
assert.strictEqual( loader.setVerboseWarning( true ), loader );
assert.equal( loader.verboseWarning, true );
} );
it( 'setAlphaTest', () => {
let loader = new RWXLoader();
assert.equal( loader.alphaTest, 0.2 );
assert.strictEqual( loader.setAlphaTest( 0.6 ), loader );
assert.equal( loader.alphaTest, 0.6 );
} );
it( 'setForceTextureFiltering', () => {
let loader = new RWXLoader();
assert.equal( loader.forceTextureFiltering, true );
assert.strictEqual( loader.setForceTextureFiltering( false ), loader );
assert.equal( loader.forceTextureFiltering, false );
} );
it( 'setCorrectInvalidNormals', () => {
let loader = new RWXLoader();
assert.equal( loader.correctInvalidNormals, false );
assert.strictEqual( loader.setCorrectInvalidNormals( true ), loader );
assert.equal( loader.correctInvalidNormals, true );
} );
it( 'setAllowURLs', () => {
let loader = new RWXLoader();
assert.equal( loader.allowURLs, false );
assert.strictEqual( loader.setAllowURLs( true ), loader );
assert.equal( loader.allowURLs, true );
} );
it( 'RWXMaterial', () => {
let rwxMat = new RWXMaterial();
assert.equal( rwxMat.color.length, 3 );
assert.equal( rwxMat.color[ 0 ], 0.0 );
assert.equal( rwxMat.color[ 1 ], 0.0 );
assert.equal( rwxMat.color[ 2 ], 0.0 );
assert.equal( rwxMat.surface.length, 3 );
assert.equal( rwxMat.surface[ 0 ], 0.69 );
assert.equal( rwxMat.surface[ 1 ], 0.0 );
assert.equal( rwxMat.surface[ 2 ], 0.0 );
assert.equal( rwxMat.opacity, 1.0 );
assert.equal( rwxMat.lightsampling, LightSampling.FACET );
assert.equal( rwxMat.geometrysampling, GeometrySampling.SOLID );
assert.equal( rwxMat.texturemodes.length, 3 );
assert.equal( rwxMat.texturemodes[ 0 ], TextureMode.LIT );
assert.equal( rwxMat.texturemodes[ 1 ], TextureMode.FORESHORTEN );
assert.equal( rwxMat.texturemodes[ 2 ], TextureMode.FILTER );
assert.equal( rwxMat.materialmode, MaterialMode.NULL );
assert.equal( rwxMat.texture, null );
assert.equal( rwxMat.tint, false );
assert.equal( rwxMat.mask, null );
assert.equal( rwxMat.specularMap, null );
assert.equal( rwxMat.normalMap, null );
assert.equal( rwxMat.textureaddressmode, TextureAddressMode.WRAP );
assert.equal( rwxMat.tag, 0 );
assert.equal( rwxMat.getMatSignature(), '0.0000.0000.000_0.6900.0000.000_1.000_1_3_123_1__false____0_true_0_1.00' );
const clonedMat = rwxMat.clone();
rwxMat.texture = 'wood1';
rwxMat.mask = 'wood1m';
rwxMat.tag = 100;
rwxMat.ratio = 0.5;
assert.equal( rwxMat.getMatSignature(), '0.0000.0000.000_0.6900.0000.000_1.000_1_3_123_1_wood1_false_wood1m___0_true_100_0.50' );
rwxMat.color[ 0 ] = 1;
rwxMat.color[ 1 ] = 2;
rwxMat.color[ 2 ] = 3;
rwxMat.surface[ 0 ] = 4;
rwxMat.surface[ 1 ] = 5;
rwxMat.surface[ 2 ] = 6;
rwxMat.opacity = 0.5;
rwxMat.lightsampling = LightSampling.VERTEX;
rwxMat.geometrysampling = GeometrySampling.WIREFRAME;
rwxMat.texturemodes.pop();
rwxMat.materialmode = MaterialMode.NONE;
rwxMat.texture = 'texture1';
rwxMat.tint = true;
rwxMat.mask = 'texture1m';
rwxMat.textureaddressmode = TextureAddressMode.CLAMP;
rwxMat.collision = false;
rwxMat.normalMap = 'texture1-n';
assert.equal( rwxMat.getMatSignature(), '1.0002.0003.000_4.0005.0006.000_0.500_2_2_12_0_texture1_true_texture1m__texture1-n_2_false_100_0.50' );
rwxMat.specularMap = 'texture1-s';
assert.equal( rwxMat.getMatSignature(), '1.0002.0003.000_4.0005.0006.000_0.500_2_2_12_0_texture1_true_texture1m_texture1-s_texture1-n_2_false_100_0.50' );
// We ensure that everything was copied, down to the methods themselves
assert.equal( typeof clonedMat.constructor, 'function' );
assert.equal( typeof clonedMat.clone, 'function' );
assert.equal( typeof clonedMat.getColorHexValue, 'function' );
assert.equal( typeof clonedMat.getMatSignature, 'function' );
assert.equal( clonedMat.color.length, 3 );
assert.equal( clonedMat.color[ 0 ], 0.0 );
assert.equal( clonedMat.color[ 1 ], 0.0 );
assert.equal( clonedMat.color[ 2 ], 0.0 );
assert.equal( clonedMat.surface.length, 3 );
assert.equal( clonedMat.surface[ 0 ], 0.69 );
assert.equal( clonedMat.surface[ 1 ], 0.0 );
assert.equal( clonedMat.surface[ 2 ], 0.0 );
assert.equal( clonedMat.opacity, 1.0 );
assert.equal( clonedMat.lightsampling, LightSampling.FACET );
assert.equal( clonedMat.geometrysampling, GeometrySampling.SOLID );
assert.equal( clonedMat.texturemodes.length, 3 );
assert.equal( clonedMat.texturemodes[ 0 ], TextureMode.LIT );
assert.equal( clonedMat.texturemodes[ 1 ], TextureMode.FORESHORTEN );
assert.equal( clonedMat.texturemodes[ 2 ], TextureMode.FILTER );
assert.equal( clonedMat.materialmode, MaterialMode.NULL );
assert.equal( clonedMat.texture, null);
assert.equal( clonedMat.tint, false);
assert.equal( clonedMat.mask, null);
assert.equal( clonedMat.textureaddressmode, TextureAddressMode.WRAP );
assert.equal( clonedMat.tag, 0 );
} );
it( 'RWXMaterialTracker', () => {
const mgr = new RWXMaterialTracker( new RWXMaterialManager() );
assert.equal( mgr.currentRWXMaterial.color.length, 3 );
assert.equal( mgr.currentRWXMaterial.color[ 0 ], 0.0 );
assert.equal( mgr.currentRWXMaterial.color[ 1 ], 0.0 );
assert.equal( mgr.currentRWXMaterial.color[ 2 ], 0.0 );
assert.equal( mgr.currentRWXMaterial.surface.length, 3 );
assert.equal( mgr.currentRWXMaterial.surface[ 0 ], 0.69 );
assert.equal( mgr.currentRWXMaterial.surface[ 1 ], 0.0 );
assert.equal( mgr.currentRWXMaterial.surface[ 2 ], 0.0 );
assert.equal( mgr.currentRWXMaterial.opacity, 1.0 );
assert.equal( mgr.currentRWXMaterial.lightsampling, LightSampling.FACET );
assert.equal( mgr.currentRWXMaterial.geometrysampling, GeometrySampling.SOLID );
assert.equal( mgr.currentRWXMaterial.texturemodes.length, 3 );
assert.equal( mgr.currentRWXMaterial.texturemodes[ 0 ], TextureMode.LIT );
assert.equal( mgr.currentRWXMaterial.texturemodes[ 1 ], TextureMode.FORESHORTEN );
assert.equal( mgr.currentRWXMaterial.texturemodes[ 2 ], TextureMode.FILTER );
assert.equal( mgr.currentRWXMaterial.materialmode, MaterialMode.NULL );
assert.equal( mgr.currentRWXMaterial.texture, null);
assert.equal( mgr.currentRWXMaterial.mask, null);
assert.equal( mgr.currentRWXMaterial.tag, 0 );
assert.equal( mgr.currentMaterialList.length, 0 );
assert.equal( mgr.getCurrentMaterialID(), 0 );
assert.equal( mgr.currentMaterialList.length, 1 );
// Change the material
mgr.currentRWXMaterial.color[ 0 ] = 0.5;
assert.equal( mgr.getCurrentMaterialID(), 1 );
assert.equal( mgr.getCurrentMaterialList().length, 2 );
// Go back to the previous material
mgr.currentRWXMaterial.color[ 0 ] = 0.0;
assert.equal( mgr.getCurrentMaterialID(), 0 );
assert.equal( mgr.getCurrentMaterialList().length, 2 );
assert.equal( mgr.getCommitedMaterialList().length, 0 );
mgr.commitMaterials();
assert.equal( mgr.getCommitedMaterialList().length, 2 );
mgr.clearCurrentMaterialList();
assert.equal( mgr.currentMaterialList.length, 0 );
assert.equal( mgr.getCommitedMaterialList().length, 0 );
} );
it( 'RWXMaterialManager', () => {
const mgr = new RWXMaterialManager();
const material = new RWXMaterial();
const signature = material.getMatSignature();
assert.equal( mgr.threeMaterialMap.size, 0 );
mgr.addRWXMaterial( material );
assert.ok( mgr.hasThreeMaterialPack( signature ) );
assert.equal( mgr.threeMaterialMap.size, 1 );
assert.equal( mgr.getThreeMaterialPack( signature ).signature, signature );
// Change the material
material.color[ 0 ] = 0.5;
mgr.addRWXMaterial( material );
assert.ok( mgr.hasThreeMaterialPack( material.getMatSignature() ) );
assert.equal( mgr.threeMaterialMap.size, 2 );
assert.equal( mgr.getThreeMaterialPack( material.getMatSignature() ).signature, material.getMatSignature() );
mgr.removeThreeMaterialPack( signature );
assert.ok( ! mgr.hasThreeMaterialPack( signature ) );
assert.equal( mgr.threeMaterialMap.size, 1 );
assert.equal( typeof mgr.getThreeMaterialPack( signature ), 'undefined' );
assert.equal( mgr.getThreeMaterialPack( material.getMatSignature() ).signature, material.getMatSignature() );
mgr.clear();
assert.equal( mgr.threeMaterialMap.size, 0 );
assert.equal( typeof mgr.getThreeMaterialPack( material.getMatSignature() ), 'undefined' );
} );
it( 'Prop loading: single mesh', async () => {
const rwx = await loadTestCube( true );
assert.ok( rwx instanceof Mesh );
assert.equal( rwx.material.length, 7 );
assert.equal( rwx.geometry.getAttribute( 'position' ).count, 6 * 4 ); // 6 faces, 4 vertices each, plus 4 wasted polygon vertices
assert.equal( rwx.geometry.getAttribute( 'position' ).array.length, 6 * 4 * 3 ); // (X, Y, Z) for each vertex
assert.equal( rwx.geometry.getIndex().count, 6 * 2 * 3 ); // 2 triangles per face, 3 vertex indices to make a triangle
assert.equal( rwx.geometry.getIndex().array.length, 6 * 2 * 3 ); // Same here
assert.equal( rwx.material[4].userData.rwx.material.tag, 100 );
const boundingBox = new Box3();
boundingBox.setFromObject( rwx );
assert.ok( isCloseTo( boundingBox.min.x, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.min.y, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.min.z, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.x, 1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.y, 1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.z, 1, epsilon ) );
// Check that vertices are overall placed in plausible spots (for a cube)
let nb_x_negative = 0;
let nb_x_positive = 0;
let nb_y_negative = 0;
let nb_y_positive = 0;
let nb_z_negative = 0;
let nb_z_positive = 0;
for ( let i = 0, positions = rwx.geometry.getAttribute( 'position' ).array; i < positions.length ; i += 3 ) {
const [ x, y, z ] = positions.slice( i, i + 3 );
if ( x > -1.01 && x < -0.99 ) {
nb_x_negative++;
}
if ( x > 0.99 && x < 1.01 ) {
nb_x_positive++;
}
if ( y > -1.01 && y < -0.99 ) {
nb_y_negative++;
}
if ( y > 0.99 && y < 1.01 ) {
nb_y_positive++;
}
if ( z > -1.01 && z < -0.99 ) {
nb_z_negative++;
}
if ( z > 0.99 && z < 1.01 ) {
nb_z_positive++;
}
}
assert.equal( nb_x_negative, 12 );
assert.equal( nb_x_positive, 12 );
assert.equal( nb_y_negative, 12 );
assert.equal( nb_y_positive, 12 );
assert.equal( nb_z_negative, 12 );
assert.equal( nb_z_positive, 12 );
} );
it( 'Prop loading: clump group', async () => {
const rwx = await loadTestCube( false );
let nbGroups = 0;
let nbMeshes = 0;
let nbMaxChildrenInGroup = 0;
let nbMaterials = 0;
let nbVertexCoords = 0;
let nbFaceIndices = 0;
const foundClumpTags = [];
const foundMaterialTags = [];
// Ready all the casters
const xCasterLeftFront = new Raycaster( new Vector3( -5, 0, -0.5 ), new Vector3( 1, 0, 0 ), 0, 5 );
const xCasterLeftBack = new Raycaster( new Vector3( 5, 0, -0.5 ), new Vector3( -1, 0, 0 ), 0, 5 );
const xCasterRightFront = new Raycaster( new Vector3( -5, 0, 0.5 ), new Vector3( 1, 0, 0 ), 0, 5 );
const xCasterRightBack = new Raycaster( new Vector3( 5, 0, 0.5 ), new Vector3( -1, 0, 0 ), 0, 5 );
const yCasterLeftFront = new Raycaster( new Vector3( 0, -5, -0.5 ), new Vector3( 0, 1, 0 ), 0, 5 );
const yCasterLeftBack = new Raycaster( new Vector3( 0, 5, -0.5 ), new Vector3( 0, -1, 0 ), 0, 5 );
const yCasterRightFront = new Raycaster( new Vector3( 0, -5, 0.5 ), new Vector3( 0, 1, 0 ), 0, 5 );
const yCasterRightBack = new Raycaster( new Vector3( 0, 5, 0.5 ), new Vector3( 0, -1, 0 ), 0, 5 );
const zCasterLeftFront = new Raycaster( new Vector3( -0.5, 0, -5 ), new Vector3( 0, 0, 1 ), 0, 5 );
const zCasterLeftBack = new Raycaster( new Vector3( -0.5, 0, 5 ), new Vector3( 0, 0, -1 ), 0, 5 );
const zCasterRightFront = new Raycaster( new Vector3( 0.5, 0, -5 ), new Vector3( 0, 0, 1 ), 0, 5 );
const zCasterRightBack = new Raycaster( new Vector3( 0.5, 0, 5 ), new Vector3( 0, 0, -1 ), 0, 5 );
let xCasterLeftCount = 0;
let xCasterRightCount = 0;
let yCasterLeftCount = 0;
let yCasterRightCount = 0;
let zCasterLeftCount = 0;
let zCasterRightCount = 0;
assert.ok( rwx instanceof Group );
assert.equal( rwx.children[0].name, scaleGroupName );
assert.equal( rwx.children[0].scale.x, 10.0 );
assert.equal( rwx.children[0].scale.y, 10.0 );
assert.equal( rwx.children[0].scale.z, 10.0 );
assert.equal( rwx.children[0].children[0].name, firstClumpName );
const boundingBox = new Box3();
boundingBox.setFromObject( rwx );
probeTestCube( rwx, ( g ) => {
nbGroups++;
if ( g.children.length > nbMaxChildrenInGroup ) {
nbMaxChildrenInGroup = g.children.length;
}
if ( g.userData?.rwx?.tag ) {
foundClumpTags.push( g.userData.rwx.tag );
}
}, ( m ) => {
const positions = m.geometry.getAttribute( 'position' ).array;
nbMeshes++;
nbMaterials += m.material.length;
nbVertexCoords += positions.length;
nbFaceIndices += m.geometry.getIndex().array.length;
xCasterLeftCount += xCasterLeftFront.intersectObjects( [ m ], false ).length + xCasterLeftBack.intersectObjects( [ m ], false ).length;
xCasterRightCount += xCasterRightFront.intersectObjects( [ m ], false ).length + xCasterRightBack.intersectObjects( [ m ], false ).length;
yCasterLeftCount += yCasterLeftFront.intersectObjects( [ m ], false ).length + yCasterLeftBack.intersectObjects( [ m ], false ).length;
yCasterRightCount += yCasterRightFront.intersectObjects( [ m ], false ).length + yCasterRightBack.intersectObjects( [ m ], false ).length;
zCasterLeftCount += zCasterLeftFront.intersectObjects( [ m ], false ).length + zCasterLeftBack.intersectObjects( [ m ], false ).length;
zCasterRightCount += zCasterRightFront.intersectObjects( [ m ], false ).length + zCasterRightBack.intersectObjects( [ m ], false ).length;
for ( const material of m.material ) {
if ( material.userData.rwx?.material?.tag ) {
foundMaterialTags.push( material.userData.rwx.material.tag );
}
}
} );
assert.equal( nbMeshes, 6 );
assert.equal( nbGroups, 11 ); // There are 6 declared clumps, 4 protoinstance statements and the scale group
assert.equal( nbMaxChildrenInGroup, 5 ); // 4 clumps and 1 protoinstance
assert.equal( nbMaterials, 7 );
assert.equal( nbVertexCoords, 6 * 4 * 3 ); // 6 faces, 4 vertices each, 3 coordinates each
assert.equal( nbFaceIndices, 6 * 2 * 3 ); // 2 triangles per face, 3 vertex indices to make a triangle
assert.equal( foundClumpTags.length, 1 );
assert.equal( foundClumpTags[0], 3 );
assert.equal( foundMaterialTags.length, 1 );
assert.equal( foundMaterialTags[0], 100 );
assert.ok( isCloseTo( boundingBox.min.x, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.min.y, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.min.z, -1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.x, 1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.y, 1, epsilon ) );
assert.ok( isCloseTo( boundingBox.max.z, 1, epsilon ) );
assert.equal( xCasterLeftCount, 2 );
assert.equal( xCasterRightCount, 2 );
assert.equal( yCasterLeftCount, 2 );
assert.equal( yCasterRightCount, 2 );
assert.equal( zCasterLeftCount, 2 );
assert.equal( zCasterRightCount, 2 );
} );
} );