import { Color, Mesh } from "three";
import { LoopSubdivision } from "three-subdivide";
/**
* 3D mesh with specific geometry and material
* @hideconstructor
*/
export class Mesh3D extends Mesh {
constructor(geometry, material) {
super(geometry, material);
}
/**
* Set the rotation along X axis
*
* @param {number} angle - rotation along X in degrees or radians
* @returns {Mesh3D} modified object
*/
setRotationX(angle) {
if (p5.instance._angleMode === p5.prototype.DEGREES) {
angle = p5.prototype.radians(angle);
}
this.rotation.x = angle;
return this;
}
/**
* Set the rotation along Y axis
*
* @param {number} angle - rotation along Y in degrees or radians
* @returns {Mesh3D} modified object
*/
setRotationY(angle) {
if (p5.instance._angleMode === p5.prototype.DEGREES) {
angle = p5.prototype.radians(angle);
}
this.rotation.y = angle;
return this;
}
/**
* Set the rotation along Z axis
*
* @param {number} angle - rotation along Z in degrees or radians
* @returns {Mesh3D} modified object
*/
setRotationZ(angle) {
if (p5.instance._angleMode === p5.prototype.DEGREES) {
angle = p5.prototype.radians(angle);
}
this.rotation.z = angle;
return this;
}
/**
*
* Set rotation of a Mesh3D.
*
* @param {Number|Vector} x rotation along X in degrees or radians or a p5.Vector object (in this case ony one parameter is needed)
* @param {Number} y rotation along Y in degrees or radians
* @param {Number} z rotation along Z in degrees or radians
* @returns {Mesh3D} modified object
*/
setRotation(x = 0, y = 0, z = 0) {
//console.log(p5.instance._angleMode)
if (x.isVector3 || x.angleBetween) {
const tempVector = {
x: x.x,
y: x.y,
z: x.z
}
x = tempVector.x;
y = tempVector.y;
z = tempVector.z;
}
if (p5.instance._angleMode === p5.prototype.DEGREES) {
x = p5.prototype.radians(x);
y = p5.prototype.radians(y);
z = p5.prototype.radians(z);
}
this.rotation.set(x, y, z);
return this;
}
/**
*
* Set X position
*
* @param {Number} X position
* @returns {Mesh3D} modified object
*/
setPositionX(value) {
this.position.x = value;
return this;
}
/**
*
* Set Y position
*
* @param {Number} Y position
* @returns {Mesh3D} modified object
*/
setPositionY(value) {
this.position.y = value;
return this;
}
/**
*
* Set Z position
*
* @param {Number} Z position
* @returns {Mesh3D} modified object
*/
setPositionZ(value) {
this.position.z = value;
return this;
}
/**
*
* Set position of a Mesh3D.
*
* @param {Number|Vector} x X coordinate or a p5.Vector with x,y,z (in this case ony one parameter is needed)
* @param {Number} y Y coordinate
* @param {Number} z Z coordinate
* @returns {Mesh3D} modified object
*/
setPosition(x = 0, y = 0, z = 0) {
if (x.isVector3 || x.angleBetween) {
const tempVector = {
x: x.x,
y: x.y,
z: x.z
}
x = tempVector.x;
y = tempVector.y;
z = tempVector.z;
}
this.position.set(x, y, z);
return this;
}
/**
* Set X scale.
*
* @param {Number} x X coordinate
* @returns {Mesh3D} modified object
*/
setScaleX(value) {
this.scale.x = value;
return this;
}
/**
* Set Y scale.
*
* @param {Number} y Y coordinate
* @returns {Mesh3D} modified object
*/
setScaleY(value) {
this.scale.y = value;
return this;
}
/**
* Set Z scale.
*
* @param {Number} z Z coordinate
* @returns {Mesh3D} modified object
*/
setScaleZ(value) {
this.scale.z = value;
return this;
}
/**
* If 1 parameter is used it can be a Number (in this case x,y,z are equals) or a Vector.
* If 3 parameters are used they will be X,Y and Z scale.
*
* @param {...any} values 1 or 3 Numbers or 1 Vector to define new scale.
* @returns modified object
*/
setScale(...values) {
if (values.length != 1 && values.length != 3) {
console.error("Use setScale with 1 or 3 parameters setScale(vector|x,y,z)")
return;
}
var x = 0.1;
var y = 0.1;
var z = 0.1;
//console.log(p5.instance._angleMode)
if (values[0].isVector3 || values[0].angleBetween) {
const tempVector = {
x: values[0].x,
y: values[0].y,
z: values[0].z
}
x = tempVector.x;
y = tempVector.y;
z = tempVector.z;
} else if (values.length == 1) {
x = values[0];
y = values[0];
z = values[0];
} else {
x = values[0];
y = values[1];
z = values[2];
}
this.scale.set(x, y, z);
return this;
}
/**
*
* Get current rotation
*
* @returns {Vector} rotation vector
*/
getRotation() {
const angleMode = p5.instance._angleMode;
const rotX = angleMode == p5.prototype.RADIANS ? this.rotation.x : p5.prototype.degrees(this.rotation.x);
const rotY = angleMode == p5.prototype.RADIANS ? this.rotation.y : p5.prototype.degrees(this.rotation.y);
const rotZ = angleMode == p5.prototype.RADIANS ? this.rotation.z : p5.prototype.degrees(this.rotation.z);
return new p5.Vector(rotX, rotY, rotZ);
}
getRotationX() {
const angleMode = p5.instance._angleMode;
return angleMode == p5.prototype.RADIANS ? this.rotation.x : p5.prototype.degrees(this.rotation.x);
}
getRotationY() {
const angleMode = p5.instance._angleMode;
return angleMode == p5.prototype.RADIANS ? this.rotation.y : p5.prototype.degrees(this.rotation.y);
}
getRotationZ() {
const angleMode = p5.instance._angleMode;
return angleMode == p5.prototype.RADIANS ? this.rotation.z : p5.prototype.degrees(this.rotation.z);
}
/**
*
* Get current position
*
* @returns {Vector} position vector
*/
getPosition() {
return new p5.Vector(this.position.x, this.position.y, this.position.z);
}
/**
*
* Get this object X position
*
* @returns {Number} position X coordinate
*/
getPositionX() {
return this.position.x;
}
/**
*
* Get this object Y position
*
* @returns {Number} position Y coordinate
*/
getPositionY() {
return this.position.y;
}
/**
*
* Get this object Z position
*
* @returns {Number} position Z coordinate
*/
getPositionZ() {
return this.position.z;
}
/**
*
* Get current scale
*
* @returns {Vector} scale vector
*/
getScale() {
return new p5.Vector(this.scale.x, this.scale.y, this.scale.z);
}
/**
*
* Get this object X scale
*
* @returns {Number} scale X coordinate
*/
getScaleX() {
return this.scale.x;
}
/**
*
* Get this object Y scale
*
* @returns {Number} scale Y coordinate
*/
getScaleY() {
return this.scale.y;
}
/**
*
* Get this object Z scale
*
* @returns {Number} scale Z coordinate
*/
getScaleZ() {
return this.scale.z;
}
/**
* Get this object roughness.
*
* @returns {Number} current roughness
*/
getRoughness() {
return this.material.roughness;
}
/**
* Get this object metalness.
*
* @returns {Number} current metalness
*/
getMetalness() {
return this.material.metalness;
}
/**
* Get this object diffuse color.
*
* @returns {Number} current diffuse color
*/
getColor() {
return this.material.color.toString();
}
/**
* Get this object opacity.
*
* @returns {Number} current opacity
*/
getOpacity() {
return this.material.opacity;
}
/**
* Get this object material.
*
* @returns {Material} a copy of this object material
*/
getMaterial = () => {
return this.material.clone();
}
/**
* Set roughness for this object. Default is 1
*
* @param {Number} value new roughness
* @returns {Mesh3D} modified object
*/
setRoughness(value) {
this.roughness = value;
this.traverse((o) => {
if (o.isMesh && o.material) {
o.material.roughness = value;
}
})
return this;
}
/**
* Set metalness for this object. Default is 0
*
* @param {Number} value new metalness
* @returns {Mesh3D} modified object
*/
setMetalness(value) {
this.material.metalness = value;
this.traverse((o) => {
if (o.isMesh && o.material) {
o.material.metalness = value;
}
})
return this;
}
/**
*
* Change object color. You can use string, another Color or r,g,b parameters.
*
* <pre>
* mesh.setColor("red");
* mesh.setColor("#ff0000");
* mesh.setColor(255, 0, 1);
* </pre>
*
* @param {Color} color new color.
* @returns {Mesh3D} modified object
*/
setColor(...args) {
var cTemp = p5.instance.color(...args);
var color = new Color(cTemp.toString('rgb'));
var newMaterial = this.material.clone();
this.material = newMaterial;
this.material.color = color;
this.traverse((o) => {
if (o.isMesh && o.material) {
o.material.color = color;
}
})
return this;
}
/**
* Set opacity for this object. Default is 1.
*
* @param {Number} value new opacity: 0 is completely transparent, 1 is opaque
* @returns {Mesh3D} modified object
*/
setOpacity = (opacity = 1.0) => {
if (this.material) {
this.material.opacity = opacity;
if (this.material.opacity < 1)
this.material.transparent = true;
else
this.material.transparent = false;
this.traverse((o) => {
if (o.isMesh && o.material) {
o.material.opacity = opacity;
if (o.material.opacity < 1)
o.material.transparent = true;
else
o.material.transparent = false;
}
})
}
return this;
}
/**
* Change the material of this object to a copy of the provided one.
*
* @param {Material} material a material to be copied
* @returns {Mesh3D} modified object
*/
setMaterial = (material) => {
this.material = material.clone();
return this;
}
/**
* Subdivides the geometry of the Mesh3D object using Loop subdivision.
* 🧪 This is an experimental feature: ⚠️ use with caution.
*
* @param {number} subdivisions - The number of subdivisions to perform.
* @param {boolean} [split=true] - Whether or not to split the vertices at boundaries.
* @param {boolean} [uvSmooth=false] - Whether or not to smooth UVs.
* @param {boolean} [preserveEdges=false] - Whether or not to preserve edges.
* @param {boolean} [flatOnly=false] - Whether or not to only apply the algorithm to flat faces.
* @param {number} [maxTriangles=3000] - The maximum number of triangles to generate.
* @returns {Mesh3D} The Mesh3D object with the updated geometry.
*/
subdivide = (subdivisions = 1, split = true, uvSmooth = false, preserveEdges = false, flatOnly = false, maxTriangles = 3000) => {
subdivisions = min(5, max(0, floor(subdivisions)));
const params = {
split: split,
uvSmooth: uvSmooth,
preserveEdges: preserveEdges,
flatOnly: flatOnly,
maxTriangles: Math.floor(maxTriangles)
};
this.geometry = LoopSubdivision.modify(this.geometry, subdivisions, params);
return this;
}
}