import { Reporter } from '../../reporter/reporter.js';
import { WrappedTexture } from '../wrapped_texture.js';
import { WrappedMaterial } from '../material/wrapped_material.js';
import { checkPropTypes } from '../../lib.js';
import { BuildableComponent } from '../component/buildable_component.js';
import { Component } from '../../component/component.js';
//import { Geometry } from '../geometry/geometry.js';
import { MaterialSet } from '../material/material_set.js';
import { Project } from '../../project.js';
//import { GLTF } from './gltf.js';
import { ObjectSpaceNormalMap, Vector2, Vector3, DoubleSide, LinearSRGBColorSpace, TextureLoader, RepeatWrapping,
BoxGeometry,
CylinderGeometry,
SphereGeometry,
Mesh,
Shape,
ExtrudeGeometry,
CurvePath,
LineCurve3
} from '../../../node_modules/three/build/three.module.js';
import { buildNodeMaterial } from '../../utilities/nodeMaterialBuilder.js';
import { buildProfiledContour } from '../../utilities/profiledContourBuilder.js';
import { applyBoxUV } from '../../utilities/applyBoxMapping.js';
/**
* ProductBuilder Generated Mesh (in development)
*
*/
// Info met profiled contour geometry
// https://discourse.threejs.org/t/profiledcontourgeometry-multimaterial/5801/10
// Info met bevel
// https://threejs.org/docs/#api/en/geometries/ExtrudeGeometry
// Lathe
// Extrude + Bevel
class GeneratedMesh extends BuildableComponent {
constructor( reporter, settings ) {
super(
reporter,
settings
);
checkPropTypes(
settings,
{
meshName: 'string',
shape: 'string', //box, circle, cone, cylinder, sphere
defaultMaterial: WrappedMaterial,
},
{
materialVariantGroup: MaterialSet,
tilingMultiplier: 'number',
aoMap: WrappedTexture,
aoMapIntensity: 'number',
normalMap: WrappedTexture,
normalMapIntensity: 'number',
shape: 'string',
dimensions: ''
}
);
// fucking typescript
if (!Object.getOwnPropertyDescriptor(this,'materialVariantGroup')) {
/** @type {MaterialSet} */
this.materialVariantGroup = settings.materialVariantGroup;
}
// Map the pkg json settings onto the instantiated class
this._tilingMultiplier = settings.tilingMultiplier
this._aoMap = settings.aoMap
this._aoMapIntensity = settings.aoMapIntensity
this._normalMap = settings.normalMap ///-> dit is dubbel
this._normalMapTiling = settings.normalMapTiling
this._normalMapScale = settings.normalMapIntensity ///-> dit is dubbel
this._meshObject = null;
}
static _exportName = {
singular: 'generatedMesh',
plural: 'generatedMeshes'
};
/**
* @param {WrappedMaterial} material
*/
async buildMeshMaterial( material ){
//get the product normal map
const normalMap = this._normalMap.content.main.medium
normalMap.colorSpace = LinearSRGBColorSpace
//normalMap.flipY = true; //deze is (nog) niet nodig
normalMap.wrapS = RepeatWrapping;
normalMap.wrapT = RepeatWrapping;
normalMap.repeat = this._normalMapTiling
const normalMapTiling = this._normalMapTiling
//create texture node with the product normal map
// const normalNode = normalMap
// ? new Nodes.TextureNode( normalMap )
// : undefined
const normalNode = normalMap
//create the material
const nodeMaterial = buildNodeMaterial(
material,
normalNode,
normalMapTiling
)
//copy the id from the currentmaterial
nodeMaterial.userData = { PB: { origin: material.userData.PB.origin } }
//add the environment map
nodeMaterial.envMap = material.envMap
this._meshObject.mesh.material = nodeMaterial
this._meshObject.mesh.material.normalScale = { x:2, y:2 }
this._meshObject.mesh.material.needsUpdate = true;
return nodeMaterial
}
async _build(part, quality, dependencies) {
this._status[ part ][ quality ].setState( 'loading');
//Cylinder with 3 materials for the side!
//https://stackoverflow.com/questions/8315546/texturing-a-cylinder-in-three-js
// const materials = [
// sideMaterial,
// topMaterial,
// bottomMaterial
// ]
// const geometry = new THREE.CylinderGeometry(5, 5, 0.5, 100)
// const mesh= new THREE.Mesh(geometry, materials)
switch (part) {
case 'UI':
// Get the thumbnail from the pkg json settings
if (this._settings.thumbnail) {
this._setContent('UI', quality, this._settings.thumbnail.content.main[quality]);
}
else {
this._setContent('UI', quality, Project.defaultImages.missing.cloneNode(true));
}
break;
case 'main':
//console.log( dependencies )
// Get the materialVariantGroup from the pkg json settings
if ( this._settings.materialVariantGroup && ! this._settings.materialVariantGroup.has( this._settings.defaultMaterial )){
// console.log( settings.materialVariantGroup )
//throw new Error( `Can not construct ${this.label} because def mat ${this._settings.defaultMaterial.name || this._settings.defaultMaterial.id} is not in its matVarGrp ${settings.materialVariantGroup.name || settings.materialVariantGroup.id}` )
}
// Create the primitive
let mesh, geometry, uvs;
switch( this.shape ){
case "box":
console.log("generate box");
const width = 2;
const height = 2;
const depth = 1;
geometry = new BoxGeometry(width, height, depth);
uvs = geometry.attributes.uv.array;
// Right and Left faces (width x height)
// Right face UVs (first 4 pairs)
for (var i = 0; i < 8; i += 2) {
uvs[i] *= depth; // Scale U (x axis) for width
uvs[i + 1] *= height; // Scale V (y axis) for height
}
// Left face UVs (next 4 pairs)
for (var i = 8; i < 16; i += 2) {
uvs[i] *= depth; // Scale U (x axis) for width
uvs[i + 1] *= height; // Scale V (y axis) for height
}
// Top and Bottom faces (width x depth)
// Top face UVs (next 4 pairs)
for (var i = 16; i < 24; i += 2) {
uvs[i] *= width; // Scale U (x axis) for width
uvs[i + 1] *= depth; // Scale V (y axis) for depth
}
// Bottom face UVs (next 4 pairs)
for (var i = 24; i < 32; i += 2) {
uvs[i] *= width; // Scale U (x axis) for width
uvs[i + 1] *= depth; // Scale V (y axis) for depth
}
// Front and Back faces (depth x height)
// Front face UVs (next 4 pairs)
for (var i = 32; i < 40; i += 2) {
uvs[i] *= width; // Scale U (x axis) for depth
uvs[i + 1] *= height; // Scale V (y axis) for height
}
// Back face UVs (last 4 pairs)
for (var i = 40; i < 48; i += 2) {
uvs[i] *= width; // Scale U (x axis) for depth
uvs[i + 1] *= height; // Scale V (y axis) for height
}
// Flag UVs for update
geometry.attributes.uv.needsUpdate = true;
break;
case "cylinder":
console.log("generate cylinder");
const cylinderDiameter = 2
const cylinderRadius = cylinderDiameter / 2
const radiusTop = cylinderRadius ;
const radiusBottom = cylinderRadius ;
const cylinderHeight = 0.05;
// The amount of segments on the side of the cylinder
const radialSegments = 16;
const heightSegments = 1
// The amount of tiling on the sides of the cyylinder.
const sideTilingX = 1;
const sideTilingY = 1
geometry = new CylinderGeometry(radiusTop, radiusBottom, cylinderHeight, radialSegments, heightSegments);
const uvs = geometry.attributes.uv.array;
const radialSegmentCount = radialSegments - 1; // +1 for wrapping
// Calculate sideUVCount to handle UVs for the sides
const sideUVCount = (radialSegments + 1) * 2 * (heightSegments + 1) ; // Sides UVs count
//console.log( "sideUVCount:", sideUVCount)
// Adjust UVs for the seam on the side of the cylinder
for (let i = 0; i < sideUVCount; i += 2) {
let u = uvs[i];
let v = uvs[i + 1];
// Check for seam condition: if this vertex is at the last radial segment
const segmentIndex = Math.floor(i / 2) % radialSegmentCount;
if (segmentIndex === radialSegments) {
u = 0; // Force U to wrap around for the last vertex
}
// Scale UVs for demonstration (adjust to your needs)
uvs[i] = u * sideTilingX; // Scale U (horizontal) by 2
uvs[i + 1] = v * sideTilingY; // Scale V (vertical) by 2
}
// Top and Bottom caps:
const topCapStart = sideUVCount; // Start of top cap UVs
let bottomCapStart = topCapStart + radialSegments * 3 * 2 ; // Start of bottom cap UVs
let bottomCapStart2 = bottomCapStart - ( radialSegments * 2 - 2 );
// Top cap UVs (scales by radiusTop)
for ( let i = topCapStart; i < bottomCapStart2 ; i += 2 ) {
// Scale U and V for the top cap based on radiusTop
uvs[i] *= cylinderDiameter //cylinderRadius //1; // Scale U for top cap (can apply custom scaling)
uvs[i + 1] *= cylinderDiameter // cylinderRadius // 1; // Scale V for top cap
}
// Bottom cap UVs (scales by radiusBottom)
for ( let i = bottomCapStart2 ; i < uvs.length ; i += 2 ) { // // length2
// Scale U and V for the bottom cap based on radiusBottom
uvs[i] *= cylinderDiameter; // Scale U (horizontal) by radiusBottom (can apply custom scaling)
uvs[i + 1] *= cylinderDiameter; // Scale V (vertical) by radiusBottom
}
// Flag UVs for update
geometry.attributes.uv.needsUpdate = true;
break;
case "sphere":
console.log( "generate sphere");
const radius = 2;
const widthSegments = 16;
const heightSegments2 = 16;
geometry = new SphereGeometry( radius, widthSegments, heightSegments2 );
break;
case "contour":
console.log("generate contour");
const shape = new Shape();
const type = "rectangle";
const contourWidth = 2
const contourDepth = 1
let contourHeight = 0.05
const bevelEnabled = false;
const bevelSize = 0.01;
switch(type){
case "rectangle":
// Define the corners of the square
const size = 1; // Length of each side of the square
// Move to the starting point (bottom-left corner)
shape.moveTo( 0, 0 );
// Draw lines to each corner of the square
shape.lineTo( contourWidth, 0 ); // Bottom-right corner
shape.lineTo( contourWidth, contourDepth ); // Top-right corner
shape.lineTo( 0, contourDepth ); // Top-left corner
shape.lineTo( 0, 0 ); // Back to bottom-left corner (close the shape)
break;
}
if( bevelEnabled ){
contourHeight = contourHeight - 2 * bevelSize
}
const extrudeSettings = {
depth: contourHeight,
bevelEnabled: bevelEnabled,
bevelSegments: 2,
steps: 2,
bevelSize: 0.01,
bevelThickness: 0.01
};
geometry = new ExtrudeGeometry( shape, extrudeSettings );
// Rotate the geometry to the XZ plane
geometry.rotateX(-Math.PI / 2);
break;
case "profile":
// Define the contour (path) for sweeping
const contourPath = new CurvePath();
// Create a path that matches your rectangle shape (XZ plane)
const profileWidth = 2;
const profileDepth = 1;
const contour = new Shape();
contour.moveTo(0, 0);
contour.lineTo(profileWidth, 0);
contour.lineTo(profileWidth, profileDepth);
contour.lineTo(0, profileDepth);
contour.lineTo(0, 0);
// Convert the shape into a path for sweeping
contourPath.add(new LineCurve3(
new Vector3(0, 0, 0),
new Vector3(profileWidth, 0, 0)
));
contourPath.add(new LineCurve3(
new Vector3(profileWidth, 0, 0),
new Vector3(profileWidth, 0, profileDepth)
));
contourPath.add(new LineCurve3(
new Vector3(profileWidth, 0, profileDepth),
new Vector3(0, 0, profileDepth)
));
contourPath.add(new LineCurve3(
new Vector3(0, 0, profileDepth),
new Vector3(0, 0, 0)
));
// Define the profile shape (circle)
const profileShape = new Shape();
const radius1 = 0.025; // Radius of the profile (circle)
profileShape.moveTo(radius1, 0);
profileShape.absarc(0, 0, radius1, 0, Math.PI * 2, false);
// Use `THREE.ExtrudeGeometry` to sweep the profile along the contour
const extrudeSettings1 = {
steps: 100,
extrudePath: contourPath // Sweep the profile along this path
};
geometry = new ExtrudeGeometry(profileShape, extrudeSettings1);
break;
case "profiledContour":
let contour3;
let semiCircleProfile;
let material = this._settings.defaultMaterial.content.main[ quality ]
material.side = DoubleSide;
let materials = [this._settings.defaultMaterial.content.main[ quality ]]
// Beveled edges
const profileShape4 = [ -0.025, 0.025, 0.025, 0.025, 0.025, 0, 0, -0.025, -0.025,-0.025 ];
const settings = {
contour: contour3,
contourShape: "round", //rectangle, round, oval, hexagon
contourDimensions: { x:1, y:1, z:64 },
contourMaterials: materials,
contourCap: true,
contourClosed: true,
profile: profileShape4, // semiCircleProfile, //beveledEdgesProfile, //
profileMaterialType: "group", //group //segment //band //face
profileMaterials: materials,
profileCap: false,
profileSmoothShading: false,
profileAlignment: "min", //alignment in x direction: min, center, max
profileMappingType: "box" // box, cylinder, plane - Not implemented yet
}
const profiledContour = buildProfiledContour(settings)
console.log( profiledContour )
mesh = profiledContour;
break
case "default":
console.warn("unknown shape for GeneratedMesh:", shape);
break;
}
if( !mesh && geomrty ){
// Create the mesh
mesh = new Mesh( geometry, this._settings.defaultMaterial.content.main[ quality ] );
}else{
console.error( "could not find generated mesh or geometry")
}
this._meshObject = mesh;
// Get the mesh from the GTLF file based on the meshName in je pkg json settings
// this._meshObject = dependencies.gltf.content.main[ quality ].gltf[ this._settings.meshName ];
// this._meshObject.mesh.material = dependencies.defaultMaterial.content.main[ quality ];
// //tiling multiplier
// if ( this.tilingMultiplier ){
// for ( let mapType of [ 'bumpMap', 'emissiveMap', 'map', 'specularMap', 'roughnessMap', 'metalnessMap', 'normalMap', 'aoMap' ])
// {
// if ( this._meshObject.mesh.material[ mapType ] )
// {
// this._meshObject.mesh.material = this._meshObject.mesh.material.clone();
// this._meshObject.mesh.material[ mapType ] = this._meshObject.mesh.material[ mapType ].clone();
// this._meshObject.mesh.material[ mapType ].repeat.x *= this._settings.tilingMultiplier;
// this._meshObject.mesh.material[ mapType ].repeat.y *= this._settings.tilingMultiplier;
// this._meshObject.mesh.material[ mapType ].needsUpdate = true;
// }
// }
// }
// if( this._aoMap ){
// //console.log( this._aoMap)
// this._meshObject.mesh.material.aoMap = this._aoMap.content.main[ quality ];
// this._meshObject.mesh.material.aoMap.needsUpdate = true;
// //this._meshObject.mesh.material.aoMap.channel = 0;
// }
// if( this._aoMapIntensity ){
// this._meshObject.mesh.material.aoMapIntensity = this._aoMapIntensity
// this._meshObject.mesh.material.aoMap.needsUpdate = true;
// }
// if( this._normalMap ){
// this.buildMeshMaterial( this._meshObject.mesh.material )
// }
// this._meshObject.mesh.castShadow = true;
// this._meshObject.mesh.receiveShadow = true;
// // const mesh = new Mesh(
// // dependencies.geometry.content.main[ quality ],
// // dependencies.defaultMaterial.content.main[ quality ]
// // );
this._setContent( 'main', quality, this._meshObject );
break;
default:
this._setContent(part, quality, null);
break;
}
}
}
export { GeneratedMesh };