/**
* @file Extention for three.js. SolidMLBufferGeometry.js depends on three.js and SolidML.js. Import after these dependent files.
*/
/**
* THREE.BufferGeometry constructed by {@link SolidML}. SolidMLBUfferGeometry.js depends on three.js and SolidML.js. Import after these dependent files.
* @extends {THREE.BufferGeometry}
*/
SolidML.BufferGeometry = class extends THREE.BufferGeometry {
/** THREE.BufferGeometry constructed by {@link SolidML} script.
* @param {string} [script] script to construct object. call {@link SolidML.BufferGeometry#build} inside.
* @param {object} [criteria] default criteria of this structure, specified by "set *" commands in script.
* @param {Object.<BufferGeometry>} [geometryHash] hash map of source geometries and keys like {"box":new THREE.BoxBufferGeometry(1, 1, 1)}. The source geometory should be indexed.
*/
constructor(script=null, criteria=null, geometryHash=null) {
super();
/**
* vertex count, same as BufferGeometry.attributes.position.count after build()
* @type {int}
*/
this.vertexCount = 0;
/**
* index count, same as BufferGeometry.index.count after build()
* @type {int}
*/
this.indexCount = 0;
/**
* object count. one continuous mesh is counted as 1 object.
* @type {int}
*/
this.objectCount = 0;
/**
* {@link SolidML} instance to construct object.
* @type {SolidML}
*/
this.solidML = null;
/**
* filtering function(SolidML.BuildStatus)=>boolean.
* @type {function}
*/
this.geometryFilter = null;
// private
this._vertexIndex = 0;
this._indexIndex = 0;
this._geometryCreator = new SolidML.GeometryCreator(geometryHash);
if (script)
this.build(script, criteria);
}
/**
* returns true when the script is already compiled
* @return {boolean}
*/
isCompiled() {
return !!this.solidML;
}
/** construct object by script. execute {@link SolidML.BufferGeometry#compile}=>{@link SolidML.BufferGeometry#estimateBufferCount}=>{@link SolidML.BufferGeometry#allocBuffers}=>{@link SolidML.BufferGeometry#update} inside
* @param {string} script script to construct object.
* @param {object} [criteria] default criteria of this structure, specified by "set *" commands in script.
* @param {boolean} [isDynamic] set THREE.BufferAttribute.dynamic
* @param {int} [indexMargin] margin for index buffer, added to requierd index buffer count
* @param {int} [vertexMargin] margin for vertex buffer, added to requierd vertex buffer count
* @param {function} [filter] filter filtering function(SolidML.BuildStatus)=>boolean.
* @return {SolidML.BufferGeometry} this instance
*/
build(script, criteria=null, isDynamic=false, indexMargin=0, vertexMargin=0, filter=null) {
this.geometryFilter = filter;
this.compile(script, criteria);
this.estimateBufferCount(indexMargin, vertexMargin);
this.allocBuffers(isDynamic);
this.update();
return this;
}
/** Parse script, make a structure of recursive calls inside.
* @param {string} script script to construct object.
* @param {object} [criteria] default criteria of this structure, specified by "set *" commands in script.
* @return {SolidML.BufferGeometry} this instance
*/
compile(script, criteria=null) {
this.solidML = new SolidML(script, criteria);
return this;
}
/** estimate index, vertex and object count with some margins. Counted values are set at {@link SolidML.BufferGeometry.indexCount}, {@link SolidML.BufferGeometry.vertexCount} and {@link SolidML.BufferGeometry.objectCount}.
* @param {int} [indexMargin] margin for index buffer, added to requierd index buffer count
* @param {int} [vertexMargin] margin for vertex buffer, added to requierd vertex buffer count
* @return {SolidML.BufferGeometry} this instance
*/
estimateBufferCount(indexMargin=0, vertexMargin=0) {
this.indexCount = indexMargin;
this.vertexCount = vertexMargin;
this.objectCount = 0;
this._geometryCreator.setup(true);
this.solidML.build(stat=>{
if (this.geometryFilter && !this.geometryFilter(stat)) return;
const geomCreated = this._geometryCreator.create(stat);
if (geomCreated) {
stat.color._incrementRandMT();
this.indexCount += geomCreated.index.array.length;
this.vertexCount += geomCreated.attributes.position.count;
this.objectCount++;
}
});
this._geometryCreator.composeMeshes(true).forEach(reference=>{
this.indexCount += reference.indexCount;
this.vertexCount += reference.vertexCount;
this.objectCount++;
});
return this;
}
/** allocate index buffer and vertex buffer
* @param {boolean} [isDynamic] set THREE.BufferAttribute.dynamic
* @param {int} [indexCount] index buffer size. pass 0 to allocate by {@link SolidML.BufferGeometry.indexCount} estimated by {@link SolidML.BufferGeometry#estimateBufferCount}
* @param {int} [vertexCount] vertex buffer size. pass 0 to allocate by {@link SolidML.BufferGeometry.vertexCount} estimated by {@link SolidML.BufferGeometry#estimateBufferCount}
* @return {SolidML.BufferGeometry} this instance
*/
allocBuffers(isDynamic=true, indexCount=0, vertexCount=0) {
if (indexCount > 0) this.indexCount = indexCount;
if (vertexCount > 0) this.vertexCount = vertexCount;
this._indices = new Uint32Array(this.indexCount);
this._positions = new Float32Array(this.vertexCount * 3);
this._normals = new Float32Array(this.vertexCount * 3);
this._colors = new Float32Array(this.vertexCount * 4);
this.setIndex(new THREE.BufferAttribute(this._indices, 1).setDynamic(isDynamic));
this.addAttribute('position', new THREE.BufferAttribute(this._positions, 3).setDynamic(isDynamic));
this.addAttribute('normal', new THREE.BufferAttribute(this._normals, 3).setDynamic(isDynamic));
this.addAttribute('color', new THREE.BufferAttribute(this._colors, 4).setDynamic(isDynamic));
return this;
}
/** update vertex buffer and send data to gpu.
* @param {THREE.Vector3} [sortingDotProduct] sort geometries by dot product with specified vector. pass null to sort by generated order. meshes are not sorted.
* @return {SolidML.BufferGeometry} this instance
*/
update(sortingDotProduct=null) {
this._indexIndex = 0;
this._vertexIndex = 0;
this._geometryCreator.setup(false);
if (sortingDotProduct) {
const statList = [], vec = sortingDotProduct;
this.solidML.build(stat=>{
if (this.geometryFilter && !this.geometryFilter(stat)) return;
statList.push(stat.reference());
});
statList.sort((statA, statB)=>{
const ma = statA.matrix.elements, mb = statB.matrix.elements,
da = ma[12]/ma[15]*vec.x + ma[13]/ma[15]*vec.y + ma[14]/ma[15]*vec.z,
db = mb[12]/mb[15]*vec.x + mb[13]/mb[15]*vec.y + mb[14]/mb[15]*vec.z;
return db - da;
}).forEach(stat=>{
this._copyGeometory(stat, this._geometryCreator.create(stat));
});
} else {
this.solidML.build(stat=>{
if (this.geometryFilter && !this.geometryFilter(stat)) return;
this._copyGeometory(stat, this._geometryCreator.create(stat));
});
}
this._geometryCreator.composeMeshes(false).forEach(geom=>this._copyGeometory(null, geom));
this.computeVertexNormals();
this.attributes.position.needsUpdate = true;
this.attributes.normal.needsUpdate = true;
this.attributes.color.needsUpdate = true;
return this;
}
_copyGeometory(stat, geom) {
if (!geom) return;
const vcount = geom.attributes.position.count,
icount = geom.index.array.length;
let invertFace = false;
if (stat) {
stat.matrix._applyToSrc_copyToDst(
geom.attributes.position.array, vcount,
this.attributes.position.array, this._vertexIndex, 3);
stat.color._fillArray(this.attributes.color.array, this._vertexIndex, vcount);
invertFace = (stat.matrix.signed_det3() < 0);
} else {
this.attributes.position.array.set(geom.attributes.position.array, this._vertexIndex*3);
this.attributes.color .array.set(geom.attributes.color .array, this._vertexIndex*4);
}
if (invertFace) {
for (let i=0; i<icount; i+=3, this._indexIndex+=3) {
this.index.array[this._indexIndex] = geom.index.array[i] + this._vertexIndex;
this.index.array[this._indexIndex+1] = geom.index.array[i+2] + this._vertexIndex;
this.index.array[this._indexIndex+2] = geom.index.array[i+1] + this._vertexIndex;
}
} else {
for (let i=0; i<icount; i++, this._indexIndex++)
this.index.array[this._indexIndex] = geom.index.array[i] + this._vertexIndex;
}
this._vertexIndex += vcount;
}
}
//
SolidML.GeometryCreator = class {
constructor(geometryHash) {
// generate hash map
const indexing = geom=>{
const indices = new Uint16Array(geom.attributes.position.count);
for (let i=0; i<indices.length; i++)
indices[i] = i;
geom.setIndex(new THREE.BufferAttribute(indices, 1));
return geom;
};
this.rotz = new THREE.Matrix4().makeRotationZ(-Math.PI/2),
this.roty = new THREE.Matrix4().makeRotationY(Math.PI/2);
// geometry hash
this._geometryHash = Object.assign({
"box": new THREE.BoxBufferGeometry(1, 1, 1),
"sphere": new THREE.SphereBufferGeometry(0.5, 8, 6),
"cylinder": new THREE.CylinderBufferGeometry(0.5, 0.5, 1, 8).applyMatrix(this.rotz),
"cone": new THREE.ConeBufferGeometry(0.5, 1, 8).applyMatrix(this.rotz),
"torus": new THREE.TorusBufferGeometry(0.5, 0.1, 4, 8).applyMatrix(this.roty),
"tetra": indexing(new THREE.TetrahedronBufferGeometry(0.5)),
"octa": indexing(new THREE.OctahedronBufferGeometry(0.5)),
"dodeca": indexing(new THREE.DodecahedronBufferGeometry(0.5)),
"icosa": indexing(new THREE.IcosahedronBufferGeometry(0.5)),
"grid": new SolidML.GridBufferGeometry(1, 0.1),
"line": new THREE.BoxBufferGeometry(1, 0.1, 0.1),
"triangle": this.__triangleGeom([1,0,0,0,1,0,0,0,1]),
"point": null,
}, geometryHash);
// cahce area
this._cache = {
"sphere": [],
"cylinder": [],
"cone": [],
"grid": [],
"line": [],
"torus": {},
"triangle": {}
};
// creator functions
this._creatorFunctions = {
"sphere": this._sphereCreator.bind(this),
"cylinder": this._cylinderCreator.bind(this),
"cone": this._coneCreator.bind(this),
"grid": this._gridCreator.bind(this),
"line": this._lineCreator.bind(this),
"torus": this._torusCreator.bind(this),
"triangle": this._triangleCreator.bind(this),
"mesh": this._meshCreator.bind(this),
"cmesh": this._cmeshCreator.bind(this),
"tube": this._tubeCreator.bind(this),
"ctube": this._ctubeCreator.bind(this),
};
}
setup(isEstimationOnly) {
// tempolaly area
this._composers = {};
this._isEstimationOnly = isEstimationOnly;
}
create(stat) {
return (stat.label in this._creatorFunctions && (!(stat.label in this._geometryHash) || stat.param || stat.option)) ?
this._creatorFunctions[stat.label](stat) : this._geometryHash[stat.label];
}
composeMeshes(isEstimationOnly) {
if (isEstimationOnly != this._isEstimationOnly)
throw Error("fatal error : MeshComposer");
return Object.keys(this._composers).map(key=>this._composers[key].create(isEstimationOnly));
}
_sphereCreator(stat) {
let segment = Number(stat.option)>>0;
if (!segment || segment<3) segment = 8;
return this._cache.sphere[segment] || (this._cache.sphere[segment] = new THREE.SphereBufferGeometry(0.5, segment, segment));
}
_cylinderCreator(stat) {
let segment = Number(stat.option)>>0;
if (!segment || segment<3) segment = 8;
return this._cache.cylinder[segment] || (this._cache.cylinder[segment] = new THREE.CylinderBufferGeometry(0.5, 0.5, 1, segment).applyMatrix(this.rotz));
}
_coneCreator(stat) {
let segment = Number(stat.option)>>0;
if (!segment || segment<3) segment = 8;
return this._cache.cone[segment] || (this._cache.cone[segment] = new THREE.ConeBufferGeometry(0.5, 1, segment).applyMatrix(this.rotz));
}
_gridCreator(stat) {
let edgeWidth = Number(stat.option)>>0;
if (!edgeWidth) edgeWidth = 10;
return this._cache.grid[edgeWidth] || (this._cache.grid[edgeWidth] = new SolidML.GridBufferGeometry(1, edgeWidth/100));
}
_lineCreator(stat) {
let lineWidth = Number(stat.option)>>0;
if (!lineWidth) lineWidth = 10;
return this._cache.line[lineWidth] || (this._cache.line[lineWidth] = new THREE.BoxBufferGeometry(1, lineWidth/100, lineWidth/100));
}
_torusCreator(stat) {
if (stat.param in this._cache.torus)
return this._cache.torus[stat.param];
const p = stat.param.split(/[\s,;:]/).map(s=>Number(s)||0);
const tube = p[0]/100 || 0.1;
const radseg = (!p[1] || p[1]<3) ? 4 : p[1];
const tubseg = (!p[2] || p[2]<3) ? 8 : p[2];
const geom = new THREE.TorusBufferGeometry(0.5, tube, radseg, tubseg).applyMatrix(this.roty);
this._cache.torus[stat.param] = geom;
return geom;
}
_triangleCreator(stat) {
if (stat.param in this._cache.triangle)
return this._cache.triangle[stat.param];
const p = stat.param.split(/[\s,;:]/).map(s=>Number(s)||0);
if (p.length > 9) p.length = 9;
const geom = this.__triangleGeom(p);
this._cache.triangle[stat.param] = geom;
return geom;
}
_meshCreator(stat) {
if (!(stat.referenceID in this._composers)) {
this._composers[stat.referenceID] = new SolidML.MeshComposer(stat, true, 0);
if (stat.lastContinuousMesh)
this._composers[stat.referenceID].compose(stat.lastContinuousMesh, this._isEstimationOnly);
}
this._composers[stat.referenceID].compose(stat, this._isEstimationOnly);
return null;
}
_cmeshCreator(stat) {
if (!(stat.referenceID in this._composers)) {
this._composers[stat.referenceID] = new SolidML.MeshComposer(stat, false, 0);
if (stat.lastContinuousMesh)
this._composers[stat.referenceID].compose(stat.lastContinuousMesh, this._isEstimationOnly);
}
this._composers[stat.referenceID].compose(stat, this._isEstimationOnly);
return null;
}
_tubeCreator(stat) {
if (!(stat.referenceID in this._composers)) {
const tubeWidth = (stat.lastContinuousMesh) ? this._composers[stat.lastContinuousMesh.referenceID]._tubeWidth : Math.pow(stat.matrix.det3(), 1/3);
this._composers[stat.referenceID] = new SolidML.MeshComposer(stat, true, tubeWidth);
if (stat.lastContinuousMesh)
this._composers[stat.referenceID].compose(stat.lastContinuousMesh, this._isEstimationOnly);
}
this._composers[stat.referenceID].compose(stat, this._isEstimationOnly);
return null;
}
_ctubeCreator(stat) {
if (!(stat.referenceID in this._composers)) {
const tubeWidth = (stat.lastContinuousMesh) ? this._composers[stat.lastContinuousMesh.referenceID]._tubeWidth : Math.pow(stat.matrix.det3(), 1/3);
this._composers[stat.referenceID] = new SolidML.MeshComposer(stat, false, tubeWidth);
if (stat.lastContinuousMesh)
this._composers[stat.referenceID].compose(stat.lastContinuousMesh, this._isEstimationOnly);
}
this._composers[stat.referenceID].compose(stat, this._isEstimationOnly);
return null;
}
__triangleGeom(p) {
const vertex = new Float32Array(9);
vertex.set(p);
const geom = new THREE.BufferGeometry();
geom.setIndex(new THREE.Uint16BufferAttribute([0,1,2], 1));
geom.addAttribute('position', new THREE.BufferAttribute(vertex, 3));
return geom;
}
}
SolidML.MeshComposer = class {
constructor(stat, isFlat, tubeWidth) {
// for composition
this._isFlat = isFlat;
this._tubeWidth = tubeWidth;
this._vertexStac = [];
this._colorStac = [];
this._vertexEstimation = 0;
// for caluculation
this._matrix = null;
this._qrt = new THREE.Quaternion();
this._dir = new THREE.Vector3();
this._v0 = new THREE.Vector3();
this._vx = new THREE.Vector3();
this._vy = new THREE.Vector3();
this._vz = new THREE.Vector3();
// cross section points Array.<THREE.Vector2>
let segment = Number(stat.option) || 0;
if (segment < 3) segment = (this._isFlat) ? 4 : 6;
this._crossSection = [];
for (let i=0; i<segment; i++) {
const rad = (i+0.5)/segment * Math.PI * 2;
this._crossSection.push(new THREE.Vector2(Math.cos(rad)*0.707, Math.sin(rad)*0.707));
}
}
compose(stat, isEstimationOnly) {
if (isEstimationOnly) {
this._vertexEstimation += this._crossSection.length;
stat.color._incrementRandMT();
} else {
if (this._matrix)
this._extendMesh(stat);
else
this._matrix = new THREE.Matrix4();
this._matrix.copy(stat.matrix);
this._colorStac.push(stat.color.getRGBA());
}
}
create(isEstimationOnly) {
if (isEstimationOnly) {
const vmax = this._vertexEstimation,
seg = this._crossSection.length,
sidefaceCount = vmax - seg,
capfaceCount = seg - 2,
vertexCount = vmax * ((this._isFlat) ? 2 : 1) + seg * 2;
return {
vertexCount,
indexCount: sidefaceCount * 6 + capfaceCount * 6
};
}
// last extention
this._extendMesh(null);
// face indexing
let i,j;
let vmax = this._vertexStac.length;
const seg = this._crossSection.length,
sidefaceCount = vmax - seg,
capfaceCount = seg - 2,
vertexCount = vmax * ((this._isFlat) ? 2 : 1) + seg * 2,
indexBuffer = new Uint16Array(sidefaceCount * 6 + capfaceCount * 6),
colorBuffer = new Float32Array(vertexCount * 4);
// sideface
const createSideface = (iface, ivertex, ioff)=>{
const ioff2 = (ioff + 1) % seg;
indexBuffer[iface * 6] = ivertex + ioff;
indexBuffer[iface * 6 + 1] = ivertex + ioff2;
indexBuffer[iface * 6 + 2] = ivertex + ioff2 + seg;
indexBuffer[iface * 6 + 3] = ivertex + ioff;
indexBuffer[iface * 6 + 4] = ivertex + ioff2 + seg;
indexBuffer[iface * 6 + 5] = ivertex + ioff + seg;
};
if (this._isFlat) {
// flat shadeing requires vertices twice
for (i=0; i<sidefaceCount; i+=seg)
for (j=0; j<seg; j++)
createSideface(i+j, i+(j&1)*vmax, j);
// color buffer
for (i=0; i<this._colorStac.length; i++)
for (j=0; j<seg; j++) {
let col = this._colorStac[i];
colorBuffer[(i*seg+j+vmax)*4] = colorBuffer[(i*seg+j)*4] = col.r;
colorBuffer[(i*seg+j+vmax)*4+1] = colorBuffer[(i*seg+j)*4+1] = col.g;
colorBuffer[(i*seg+j+vmax)*4+2] = colorBuffer[(i*seg+j)*4+2] = col.b;
colorBuffer[(i*seg+j+vmax)*4+3] = colorBuffer[(i*seg+j)*4+3] = col.a;
}
this._vertexStac = this._vertexStac.concat(this._vertexStac);
vmax = this._vertexStac.length;
} else {
// smooth shading
for (i=0; i<sidefaceCount; i+=seg)
for (j=0; j<seg; j++)
createSideface(i+j, i, j);
// color buffer
for (i=0; i<this._colorStac.length; i++)
for (j=0; j<seg; j++) {
let col = this._colorStac[i];
colorBuffer[(i*seg+j)*4] = col.r;
colorBuffer[(i*seg+j)*4+1] = col.g;
colorBuffer[(i*seg+j)*4+2] = col.b;
colorBuffer[(i*seg+j)*4+3] = col.a;
}
}
// cap face
for (i=0; i<seg; i++) {
this._vertexStac.push(this._vertexStac[i]);
colorBuffer[(vmax+i)*4] = colorBuffer[i*4];
colorBuffer[(vmax+i)*4+1] = colorBuffer[i*4+1];
colorBuffer[(vmax+i)*4+2] = colorBuffer[i*4+2];
colorBuffer[(vmax+i)*4+3] = colorBuffer[i*4+3];
}
for (i=0; i<seg; i++) {
this._vertexStac.push(this._vertexStac[vmax-seg+i]);
colorBuffer[(vmax+seg+i)*4] = colorBuffer[(vmax-seg+i)*4];
colorBuffer[(vmax+seg+i)*4+1] = colorBuffer[(vmax-seg+i)*4+1];
colorBuffer[(vmax+seg+i)*4+2] = colorBuffer[(vmax-seg+i)*4+2];
colorBuffer[(vmax+seg+i)*4+3] = colorBuffer[(vmax-seg+i)*4+3];
}
for (i=0; i<capfaceCount; i++) {
indexBuffer[(sidefaceCount * 2 + i) * 3] = vmax;
indexBuffer[(sidefaceCount * 2 + i) * 3 + 1] = vmax + i + 2;
indexBuffer[(sidefaceCount * 2 + i) * 3 + 2] = vmax + i + 1;
indexBuffer[(sidefaceCount * 2 + capfaceCount + i) * 3] = vmax + seg;
indexBuffer[(sidefaceCount * 2 + capfaceCount + i) * 3 + 1] = vmax + seg + i + 1;
indexBuffer[(sidefaceCount * 2 + capfaceCount + i) * 3 + 2] = vmax + seg + i + 2;
}
// create geometory
const geom = new THREE.BufferGeometry(),
positions = new Float32Array(this._vertexStac.length*3);
geom.setIndex(new THREE.BufferAttribute(indexBuffer, 1));
geom.addAttribute('position', new THREE.BufferAttribute(positions, 3).copyVector3sArray(this._vertexStac));
geom.addAttribute('color', new THREE.BufferAttribute(colorBuffer, 4));
return geom;
}
_extendMesh(stat) {
const pme = this._matrix.elements;
this._v0.set(pme[12]/pme[15], pme[13]/pme[15], pme[14]/pme[15]);
if (stat) {
// update extending directions quarternion
const me = stat.matrix.elements;
this._dir.set(me[12]/me[15], me[13]/me[15], me[14]/me[15]).sub(this._v0).normalize();
this._qrt.setFromUnitVectors(this._vx.set(pme[0], pme[1], pme[2]).normalize(), this._dir);
}
if (this._tubeWidth > 0) {
// keep constant width
this._vy.set(pme[4], pme[5], pme[6]).normalize().multiplyScalar(this._tubeWidth).applyQuaternion(this._qrt);
this._vz.set(pme[8], pme[9], pme[10]).normalize().multiplyScalar(this._tubeWidth).applyQuaternion(this._qrt);
} else {
// rotate
this._vy.set(pme[4], pme[5], pme[6]).applyQuaternion(this._qrt);
this._vz.set(pme[8], pme[9], pme[10]).applyQuaternion(this._qrt);
}
for (let i=0; i<this._crossSection.length; i++) {
const v2 = this._crossSection[i];
this._vertexStac.push(this._vy.clone().multiplyScalar(v2.x).addScaledVector(this._vz, v2.y).add(this._v0));
}
}
}
SolidML.GridBufferGeometry = class extends THREE.BufferGeometry {
constructor(size, edgeWidth) {
super();
const l = size / 2, s = l - edgeWidth;
const positions = new Float32Array(24*6*3), indices = new Uint16Array(48*6);
const srcv = [new THREE.Vector3(-l,-l,l), new THREE.Vector3(l,-l,l), new THREE.Vector3(l,l,l), new THREE.Vector3(-l,l,l),
new THREE.Vector3(-s,-s,l), new THREE.Vector3(s,-s,l), new THREE.Vector3(s,s,l), new THREE.Vector3(-s,s,l),
new THREE.Vector3(-s,-s,l), new THREE.Vector3(s,-s,l), new THREE.Vector3(s,s,l), new THREE.Vector3(-s,s,l),
new THREE.Vector3(-s,-s,s), new THREE.Vector3(s,-s,s), new THREE.Vector3(s,s,s), new THREE.Vector3(-s,s,s),
new THREE.Vector3(-s,-s,l), new THREE.Vector3(s,-s,l), new THREE.Vector3(s,s,l), new THREE.Vector3(-s,s,l),
new THREE.Vector3(-s,-s,s), new THREE.Vector3(s,-s,s), new THREE.Vector3(s,s,s), new THREE.Vector3(-s,s,s)];
const srci = [0,1,4,4,1,5, 1,2,5,5,2,6, 2,3,6,6,3,7, 3,0,7,7,0,4, 8,9,12,12,9,13, 17,18,21,21,18,22, 10,11,14,14,11,15, 19,16,23,23,16,20];
let vidx = 0, iidx = 0;
const face = (fidx,xx,xy,xz,yx,yy,yz,zx,zy,zz)=>{
for (let i=0; i<24; i++, vidx+=3) {
positions[vidx] = srcv[i].x * xx + srcv[i].y * yx + srcv[i].z * zx;
positions[vidx+1] = srcv[i].x * xy + srcv[i].y * yy + srcv[i].z * zy;
positions[vidx+2] = srcv[i].x * xz + srcv[i].y * yz + srcv[i].z * zz;
}
for (let j=0; j<48; j++)
indices[iidx++] = srci[j]+fidx*24;
};
face(0, 1, 0, 0, 0, 1, 0, 0, 0, 1);
face(1, 0, 1, 0, 0, 0, 1, 1, 0, 0);
face(2, 0, 0, 1, 1, 0, 0, 0, 1, 0);
face(3, 1, 0, 0, 0,-1, 0, 0, 0,-1);
face(4, 0, 1, 0, 0, 0,-1, -1, 0, 0);
face(5, 0, 0, 1, -1, 0, 0, 0,-1, 0);
this.setIndex(new THREE.BufferAttribute(indices, 1));
this.addAttribute('position', new THREE.BufferAttribute(positions, 3));
this.computeVertexNormals();
}
}