import * as THREE from 'three'
import { loadSheet } from './Sheet.js';
import { AmbientLight, CanvasTexture, Color, DoubleSide, MeshStandardMaterial, PerspectiveCamera, PointLight, Shape, Vector3, WebGLRenderer } from 'three';
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { VertexNormalsHelper } from 'three/examples/jsm/helpers/VertexNormalsHelper.js';
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils";
import { FontLoader } from 'three/examples/jsm/loaders/FontLoader.js';
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { GlitchPass } from 'three/examples/jsm/postprocessing/GlitchPass.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
import { HalftonePass } from 'three/examples/jsm/postprocessing/HalftonePass.js';
import { LuminosityShader } from 'three/examples/jsm/shaders/LuminosityShader.js';
import { SobelOperatorShader } from 'three/examples/jsm/shaders/SobelOperatorShader.js';
import { RenderPixelatedPass } from 'three/examples/jsm/postprocessing/RenderPixelatedPass.js';
import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass.js';
import { GroundProjectedEnv } from './GroundProjectedEnv.js';
import { getDynamicEnv } from './DynamicEnvMap.js';
import { exporter } from './Exporters.js'
import { ModelViewerScene } from './ModelViewerScene.js';
import { getGeometry, setFont3D, setQuality } from './Geometry.js';
import { GPUPicker } from './GPUPicker.js';
import { Group3D } from './Group3D.js';
import { Mesh3D } from './Mesh3D.js';
import { Inspector } from './Inspector.js';
import { booleanOperation } from './BooleanOperations.js';
import { getUIButton, insertStyleSheet } from './UI.js';
import { loadAsset, loadObject } from './Importers.js';
import * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';
import { AnaglyphEffect } from 'three/examples/jsm/effects/AnaglyphEffect.js';
import { FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls.js';
import { notify, REASON_CANNOT, REASON_ERROR, REASON_CHECK } from './SmartNotifications.js';
var renderer, cam, scene, picker, orbit;
var composer;
var anaglyphEffect;
var defaultEnvironment;
var toolboxContainer;
const FULL_HD = 'Full HD';
const HD = 'HD';
const SD = 'SD';
const LD = 'LD';
const FORWARD = 'forward';
const BACKWARD = 'backward';
p5.prototype.GroundProjectedEnv = GroundProjectedEnv;
p5.prototype.getDynamicEnv = getDynamicEnv;
//Environments constants
const CINEMA = "https://dev.codemotionkids.com/libraries/diorama/environments/cinema/";
const SWITZERLAND = "https://dev.codemotionkids.com/libraries/diorama/environments/switzerland/";
const ROTUNDA = "https://dev.codemotionkids.com/libraries/diorama/environments/rotunda/";
const SUNSET = "https://dev.codemotionkids.com/libraries/diorama/environments/sunset/";
const NIGHT = "https://dev.codemotionkids.com/libraries/diorama/environments/night/";
const NEON = "https://dev.codemotionkids.com/libraries/diorama/environments/neon/";
const VERANDA = "https://dev.codemotionkids.com/libraries/diorama/environments/veranda/";
const STARS = "https://dev.codemotionkids.com/libraries/diorama/environments/stars/";
const FOREST = "https://dev.codemotionkids.com/libraries/diorama/environments/forest/";
const UBUNTU = "https://dev.codemotionkids.com/libraries/diorama/fonts/Ubuntu_Regular.json";
const presets = [SUNSET, CINEMA, ROTUNDA, SWITZERLAND, NIGHT, NEON, VERANDA, STARS, FOREST];
const GLITCH = 0;
const LUMINOSITY = 1;
const SOBEL = 2;
const BLOOM = 3;
const PIXEL = 4;
const HALFTONE = 5;
const DEPTH = 6;
const HALFTONE_DOT = 1;
const HALFTONE_ELLIPSE = 2;
const HALFTONE_LINE = 3;
const HALFTONE_SQUARE = 4;
const ORTHOGRAPHIC = 0;
const PERSPECTIVE = 1;
const CAMERA_DEFAULT_POSITION_PERSPECTIVE = new Vector3(0, 1.68, 5);
const CAMERA_DEFAULT_POSITION_ORTHOGRAPHIC = new Vector3(8, 8, 8);
const groupStack = [];
const FLAT = true;
const SMOOTH = false;
var currentShading = SMOOTH;
var inspector;
var canvasCreated = false;
var currentScene;
var currentNormalMap;
var currentDiffuseMap;
var currentRoughnessMap;
var currentAOMap;
var currentAOMapIntensity = 1.0;
var currentMetalnessMap;
var currentDisplacementMap;
var currentDisplacementScale = 1.0;
var currentBumpMap;
var currentBumpScale = 1.0;
var currentEnvMap;
var currentMaterial = new MeshStandardMaterial();
var currentColor = new Color();
var currentExtrude = null;
var currentLathe = null;
var currentLatheSides = 32;
var currentExtrudeShape;
var currentSubdivisions = {
subdivisions: 0,
split: true, // optional, default: true
uvSmooth: false, // optional, default: false
preserveEdges: false, // optional, default: false
flatOnly: false, // optional, default: false
maxTriangles: 3000, // optional, default: 3000
};;
const NONE = -1;
const EXTRUDING = 0;
const SHAPING = 1;
const LATHING = 2;
var vertexContext = NONE;
var currentShape = null;
var currentAlign = ['center'];
var last = null;
var fontLoader;
/**
*
* Load a font in typeface.js format. You can convert a font file to JSON using https://gero3.github.io/facetype.js/
* You can also use constants like <pre>UBUNTU</pre>
*
* @param {String} url typeface.js json file
* @param {Function} callback Optional function called when file is loaded
* @returns {Font} loaded font
*/
const loadFont3D = (url, callback) => {
if (!fontLoader) {
fontLoader = new FontLoader();
}
const result = {};
fontLoader.load(
// resource URL
url,
// onLoad callback
function (font) {
// do something with the font
result.font = font;
self._decrementPreload();
if (typeof callback == 'function') {
callback(result); // do the callback.
}
},
// onProgress callback
null,
// onError callback
function (err) {
notify('An error happened loading font from ' + url, REASON_ERROR);
});
return result;
};
/**
*
* Add a 3D text to your scene.
* Remember to {@link loadFont3D}
*
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @param {String} text
* @returns Text in 3D
*/
const text3D = (x = 0, y = 0, z = 0, text = "?!?") => {
const geometry = getGeometry({
type: 'text',
text: text
})
if (geometry)
return addMesh(x, y, z, geometry);
}
/**
* Transforms a 3D position into a 2D position on the screen.
*
* @param {...(number|THREE.Vector3)} args - If one argument is provided, it should be a Vector3 representing the position in 3D space. If three arguments are provided, they should be x, y, and z coordinates in 3D space.
* @returns {THREE.Vector3|null} - Returns a Vector3 with x and y coordinates representing the position on the screen, or null if the input arguments are not valid.
*/
const onScreen = (...args) => {
var position;
if (args.length == 1) {
position = args[0];
} else if (args.length == 3) {
position = new THREE.Vector3(args[0], args[1], args[2]);
} else {
console.warn("You must use onScreen() with x, y, z or a Vector3");
return null;
}
var onScreen = position.clone();
onScreen.project(getCamera());
onScreen.x = floor(((onScreen.x + 1) * width) * 0.5);
onScreen.y = floor((-(onScreen.y - 1) * height) * 0.5);
onScreen.z = 0;
return onScreen;
}
const getPresets = () => {
return presets
}
/**
* Create a 3D canvas containing your 3D scene
* @param {Integer} width - How large
* @param {Integer} height - How tall
*/
const createCanvas3D = (width, height, disableControls = false) => {
//noCanvas();
canvasCreated = true;
document.body.appendChild(renderer.domElement);
inspector = new Inspector();
if (width && height) {
p5.instance.createCanvas(width, height);
}
if (p5.instance.canvas) {
renderer.setSize(p5.instance.width, p5.instance.height);
p5.instance.canvas.style.position = "absolute";
const boundingRect = p5.instance.canvas.getBoundingClientRect();
renderer.domElement.style = `position: absolute; top: ${boundingRect.y + boundingRect.top}; left: ${boundingRect.x + boundingRect.left}; z-index: ${p5.instance.canvas.style.zIndex - 1}`;
cam.aspect = p5.instance.width / p5.instance.height;
cam.updateProjectionMatrix();
// p5.instance.canvas.addEventListener('resize', () => {
// console.log(p5.instance.width, p5.instance.height)
// renderer.setSize(p5.instance.width, p5.instance.height);
// cam.aspect = p5.instance.width / p5.instance.height;
// cam.updateProjectionMatrix();
// })
}
// p5.instance._setProperty("width", width);
// p5.instance._setProperty("height", height);
// p5.instance._setProperty("canvas", renderer.domElement);
orbit = new OrbitControls(cam, p5.instance.canvas);
if (disableControls)
orbit.enabled = false;
p5.instance.angleMode(p5.prototype.DEGREES);
p5.instance.quality(HD);
const downloadButton = getUIButton("๐ก Download", "#ff0040", () => {
exportGLTF(scene)
})
// const downloadButtonPrint = getUIButton("STL", "#4caf50", () => {
// exportSTL(scene)
// })
const downloadButtonPNG = getUIButton("๐ผ๏ธ Screenshot", "#ff5722", () => {
exportPNG()
})
const helpButton = getUIButton("๐ Help", "#2196f3", () => {
window.open('https://dev.codemotionkids.com/libraries/diorama/docs/', '_blank');
});
const exportButton = getUIButton("โจ Export", "#3f51b5", () => {
exportObject(scene)
})
toolboxContainer = document.createElement('div');
toolboxContainer.classList.add('diorama-toolbox');
toolboxContainer.appendChild(downloadButton);
toolboxContainer.appendChild(downloadButtonPNG);
toolboxContainer.appendChild(helpButton);
toolboxContainer.appendChild(exportButton);
renderer.domElement.after(toolboxContainer);
const boundingRectRenderer = renderer.domElement.getBoundingClientRect();
toolboxContainer.style.top = boundingRectRenderer.height + "px";
return renderer.domElement;
};
const resizeCanvas3D = (w, h) => {
p5.instance.resizeCanvas(w, h);
renderer.setSize(p5.instance.width, p5.instance.height);
const boundingRectRenderer = p5.instance.canvas.getBoundingClientRect();
renderer.domElement.style = `position: absolute; top: ${boundingRectRenderer.y + boundingRectRenderer.top}; left: ${boundingRectRenderer.x + boundingRectRenderer.left}; z-index: ${p5.instance.canvas.style.zIndex - 1}`;
cam.aspect = p5.instance.width / p5.instance.height;
cam.updateProjectionMatrix();
toolboxContainer.style.top = boundingRectRenderer.height + "px";
}
const init3D = () => {
renderer = new WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'low-power'
});
renderer.toneMapping = THREE.LinearToneMapping;
renderer.needsUpdate = true;
scene = new ModelViewerScene(renderer);
defaultEnvironment = scene.environment;
cam = new PerspectiveCamera(
75,
1, //aspect
0.1,
1000
);
//renderer.addRenderPass(scene, cam);
cam.position.z = 5;
cam.position.y = 1.68;
currentScene = scene;
picker = new GPUPicker(renderer, scene, cam);
if (exporter) {
exporter(renderer, cam);
}
insertStyleSheet();
};
const render3D = function () {
if (!canvasCreated)
return;
if (renderer.stats)
renderer.stats.update();
// info = renderer.info.render.triagles;
if (orbit && orbit.enabled)
orbit.update();
if (anaglyphEffect) {
anaglyphEffect.render(scene, cam);
} else if (composer && composer.passes.length > 1)
composer.render();
else
renderer.render(scene, cam);
};
/**
* Enables or disables the anaglyph effect, which creates a 3D stereoscopic image from two 2D images.
*
* To enable this effect simply call anaglyph() function. anaglyph(false) will disable it.
*
* @param {boolean} enabled - A flag indicating whether the anaglyph effect should be enabled or disabled.
* @returns {boolean} Current status of the anaglyph effect.
*/
const anaglyph = (enabled = true) => {
if (enabled) {
notify("You need 3D glasses (with red and cyan lenses) to see this!", "๐ด-๐ต")
anaglyphEffect = new AnaglyphEffect(renderer, width, height);
} else {
anaglyphEffect = null;
}
return enabled
}
/**
* Remove all special effects.
*/
const clearFX = () => {
while (composer.passes.length > 1) {
popFX();
}
isComposerNeeded();
}
/**
* Adds an effect to the composer for post-processing visual effects.
*
* @param {FX} effect - Effect type: BLOOM, LUMINOSITY, GLITCH, SOBEL, HALFTONE.
* @param {...any} params - Additional parameters for the effect, if required.
* @returns {any} The effect pass that was added to the composer.
*
* @example
* // add a luminosity effect
* pushFX(LUMINOSITY):
*
* @example
* // add a default bloom effect
* pushFX(BLOOM);
*
* @example
* // add a custom bloom effect with threshold = 1.0, strength = 1.0, and radius = 0.5
* pushFX(BLOOM, 1.0, 1.0, 0.5);
*
* @example
* // add a pixel effect with pixelSize = 10
* pushFX(PIXEL, 10);
*
* @example
* // add a halftone effect with radius = 9.5, shape = HALFTONE_SQUARE, scattered = 0, grayscale = true
* pushFX(HALFTONE, 9.5, HALFTONE_SQUARE, 0, true);
* @example
* // add a halftone effect with other available shapes like HALFTONE_DOT, HALFTONE_ELLIPSE and HALFTONE_LINE
* pushFX(HALFTONE, 12, HALFTONE_LINE);
*
*
*/
const pushFX = (effect = LUMINOSITY, ...params) => {
if (!composer) {
const renderScene = new RenderPass(scene, cam);
composer = new EffectComposer(renderer);
composer.addPass(renderScene);
}
var addedPass;
const canvasSizeAsVector = new THREE.Vector2(p5.instance.width, p5.instance.height)
if (effect == GLITCH) {
const glitchPass = new GlitchPass();
composer.addPass(glitchPass);
addedPass = glitchPass;
} else if (effect == LUMINOSITY) {
const effectGrayScale = new ShaderPass(LuminosityShader);
composer.addPass(effectGrayScale);
addedPass = effectGrayScale;
} else if (effect == SOBEL) {
const effectSobel = new ShaderPass(SobelOperatorShader);
effectSobel.uniforms['resolution'].value = canvasSizeAsVector;
effectSobel.uniforms['resolution'].value.multiplyScalar(window.devicePixelRatio);
composer.addPass(effectSobel);
addedPass = effectSobel;
} else if (effect == BLOOM) {
const bloomPass = params.length > 0 ? new UnrealBloomPass(canvasSizeAsVector, ...params) : new UnrealBloomPass(canvasSizeAsVector, 1.3, 1, 0.35);
composer.addPass(bloomPass);
addedPass = bloomPass;
} else if (effect == PIXEL) {
var pixelPass = new ShaderPass(RenderPixelatedPass);
pixelPass.uniforms['resolution'].value = canvasSizeAsVector;
pixelPass.uniforms['resolution'].value.multiplyScalar(window.devicePixelRatio);
pixelPass.uniforms['pixelSize'].value = params.length > 0 ? params[0] : 4;
composer.addPass(pixelPass);
addedPass = pixelPass;
} else if (effect == HALFTONE) {
const halftoneparams = params && params.length > 0 ? {
radius: params[0] ? params[0] : 4,
scatter: params[2] ? params[2] : 0,
shape: params[1] ? params[1] : HALFTONE_DOT,
greyscale: params[3] ? params[3] : false,
rotateR: params[4] ? radians(params[4]) : Math.PI / 12,
rotateB: params[5] ? radians(params[5]) : Math.PI / 12 * 2,
rotateG: params[6] ? radians(params[6]) : Math.PI / 12 * 3,
blending: params[7] ? params[7] : 1,
blendingMode: params[8] ? params[8] : 1
} : {};
const halftonePass = new HalftonePass(window.innerWidth, window.innerHeight, halftoneparams);
composer.addPass(halftonePass);
addedPass = halftonePass;
}
//TODO
// else if (effect == DEPTH) {
// const ssaoPass = new SSAOPass(getScene(), getCamera(), width, height);
// ssaoPass.output = SSAOPass.OUTPUT.Depth;
// composer.passes[0].enabled = false;
// composer.addPass(ssaoPass);
// addedPass = ssaoPass;
// }
return addedPass;
}
const popFX = () => {
if (composer.passes.length > 1) {
composer.removePass(composer.passes[composer.passes.length - 1])
}
isComposerNeeded();
}
const getFX = (index) => {
var selectedPass = index < composer.passes.length && index > 0 ? composer.passes[index] : null;
return selectedPass;
}
const removeFX = (index) => {
if (index < composer.passes.length && index > 0) {
composer.removePass(composer.passes[index])
}
isComposerNeeded();
}
const isComposerNeeded = () => {
if (composer.passes.length <= 1) {
composer.dispose();
composer = null;
}
}
/**
*
* Use getOrbit().disable() to block the camera view. getOrbit().enable() get back mouse controlled view.
*
* @returns Camera orbit controls
*/
const getOrbit = () => {
return orbit;
}
const color3D = (...args) => {
var cTemp = p5.instance.color(...args);
return new Color(cTemp.toString('rgb'));
}
/**
* Generate a fully saturated random color or a random color with min/max bounds.
* For example you may want a random color per object so you can call <pre>fill(randomColor())</pre>
*
*
* @param {number} minHue Default 0
* @param {number} maxHue Default 360
* @param {number} minSaturation Default 100
* @param {number} maxSaturation Default 100
* @param {number} minLightness Default 50
* @param {number} maxLightness Default 50
* @returns {Color} - Random fully saturated color or as specificed by parameters
*/
const randomColor = (minHue = 0, maxHue = 360, minSaturation = 100, maxSaturation = 100, minLightness = 50, maxLightness = 50) => {
const colorString = `hsl(${Math.round(p5.instance.random(minHue, maxHue))}, ${Math.round(p5.instance.random(minSaturation, maxSaturation))}%, ${Math.round(p5.instance.random(minLightness, maxLightness))}%)`;
return p5.instance.color(colorString);
}
/**
* Set the background color or texture of your 3D scene
* @param {Color|Image} colorOrImage - A color representation like 255, 0, 255 or "#ff00ff", or an Image object.
*/
const background3D = (...args) => {
if (args && args[0].drawingContext) {
noSkybox();
getScene().background = getTextureFromImage(args[0]);
return
}
getScene().background = null;
const newColor = color3D(...args);
renderer.setClearColor(newColor);
const alpha = p5.instance.alpha(p5.instance.color(...args));
renderer.setClearAlpha(p5.instance.map(alpha, 0, 255, 0.0, 1.0));
return newColor;
};
const equirectangularToCubemap = (equirectangular, size = 512) => {
const cubeScene = new THREE.Scene();
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(size, {
format: THREE.RGBFormat,
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter
});
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
cubeScene.add(cubeCamera);
const material = new THREE.MeshBasicMaterial({
map: equirectangular,
side: THREE.BackSide
});
const geometry = new THREE.SphereGeometry(100, 100, 100);
const mesh = new THREE.Mesh(geometry, material);
cubeScene.add(mesh);
cubeCamera.update(getRenderer(), cubeScene);
const cubeMap = cubeCamera.renderTarget.texture;
return cubeMap
};
/**
* Sets the skybox background of the 3D scene using an equirectangular image.
* If no equirectangularImage is provided, checks if a skybox background is already set.
*
* @param {Image} equirectangularImage - An equirectangular image used to create the skybox background.
* @returns {Image} - The skybox background texture.
*/
const skybox = (equirectangularImage, attachToGrid = true, height = 20, radius = 200) => {
if (!equirectangularImage) {
return getScene().background != null && getScene().background.isCubeTexture
}
var equi;
if (equirectangularImage.isCubeTexture) {
equi = equirectangularImage;
} else if (isImage(equirectangularImage)) {
equi = getTextureFromImage(equirectangularImage);
}
if (attachToGrid) {
var env = new GroundProjectedEnv(equi, {
height: height,
radius: radius
});
env.scale.setScalar(100);
if (currentGroundProjectedEnv) {
scene.remove(currentGroundProjectedEnv);
}
currentGroundProjectedEnv = env;
scene.add(currentGroundProjectedEnv);
} else {
if (currentGroundProjectedEnv) {
scene.remove(currentGroundProjectedEnv);
}
getScene().background = equirectangularToCubemap(equi);
}
return equi
}
const noSkybox = () => {
if (currentGroundProjectedEnv) {
scene.remove(currentGroundProjectedEnv);
}
getScene().background = null;
}
/**
* Set the color of your next 3D objects
*
* @param {Color} color - A color rapresentation like 255, 0, 255 or "#ff00ff"
*/
const diffuse = (...args) => {
currentColor = color3D(...args);
return currentColor.clone();
}
/**
* Get currently used diffuse color
*
* @returns {Color} current diffuse color
*/
const getDiffuse = () => {
return currentColor.toString();
}
const getTextureFromImage = (img) => {
const ctx = img.drawingContext;
return new CanvasTexture(ctx.canvas)
}
const noEnvMap = () => {
currentEnvMap = null;
}
/**
* Stop using a diffuse map when creating objects.
*/
const noDiffuseMap = () => {
currentDiffuseMap = null;
}
const noRoughnessMap = () => {
currentRoughnessMap = null;
}
const noMetalnessMap = () => {
currentMetalnessMap = null;
}
const noDisplacementMap = () => {
currentDisplacementMap = null;
}
const noNormalMap = () => {
currentNormalMap = null;
}
const noBumpMap = () => {
currentBumpMap = null;
}
/**
* Creates a new point light with the specified color, position, and other parameters,
* and adds it to the current scene.
*
* @param {Color} [color="#ffffff"] - The color of the light, in hexadecimal format.
* @param {number} [x=0] - The X coordinate of the light's position.
* @param {number} [y=0] - The Y coordinate of the light's position.
* @param {number} [z=0] - The Z coordinate of the light's position.
* @param {number} [intensity=1.0] - The intensity of the light.
* @param {number} [distance=0] - The maximum distance from the light at which objects
* should still be illuminated.
* @param {number} [decay=2] - The rate at which the light's intensity decays over distance.
*
* @returns {PointLight} The newly created point light.
* @example
*
// Add a light to a scene with or without environment
function setup() {
createCanvas3D(600, 400);
background3D("#333333");
//If you want only your lights you must use
//noEnvironment()
environment(STARS)
var lightX = 4;
var lightY = 2;
var lightZ = 2;
var lightIntensity = 0.5; //don't exagerate๐ก
pointLight("#9C27B0", lightX, lightY, lightZ, 0.5);
diffuse("white");
sphere(0, 0, 0, 1);
}
function draw() {}
*/
const pointLight = (color = "#ffffff", x = 0, y = 0, z = 0, intensity = 1.0, distance = 0, decay = 2) => {
const lightColor = color3D(color);
const newLight = new PointLight(lightColor, intensity, distance, decay);
newLight.position.set(x, y, z)
scene.add(newLight);
return newLight;
}
/**
* Add an ambient light.
*
* @param {Color} color Color of the light
* @param {Number} intensity
* @returns Newly created light object
*/
const ambientLight = (color = "#ffffff", intensity = 1.0) => {
const lightColor = color3D(color);
const newLight = new AmbientLight(lightColor, intensity);
scene.add(newLight);
return newLight;
}
const envMap = (img) => {
currentEnvMap = getTextureFromImage(img);
currentEnvMap.mapping = THREE.EquirectangularReflectionMapping;
currentEnvMap.encoding = THREE.sRGBEncoding;
}
/**
* Use an image as texture for all following 3D objects.
* Use noDiffuseMap() to stop using a texture
*
* @param {p5.Image} img
*
* @example
var img; // ๐ You need a "global variable" when loading images
function preload() {
// ๐งฑ always preload your textures
img = loadImage("texture.png");
}
function setup() {
createCanvas3D(600, 400);
//Change background color of the scene
background3D("#333333");
//Use img as texture for following objects
diffuse("white"); //Texture as it is needs white diffuse color
diffuseMap(img);
sphere(0, 0, 0, 1);
noDiffuseMap();// Remove texture
diffuse("#2196F3"); //Use a diffuse color as usual
sphere(2.5, 0, 0, 1);
diffuse("orange"); //Color your texture of orange
diffuseMap(img);
sphere(-2.5, 0, 0, 1);
}
function draw() {
// ๐ Never create objects here, but don't forget to have it in your code:
// leave empty for the moment
}
*/
const diffuseMap = (img) => {
currentDiffuseMap = getTextureFromImage(img);
}
const roughnessMap = (img) => {
currentRoughnessMap = getTextureFromImage(img);
}
const ambientOcclusionMap = (img, intensity = 1.0) => {
currentAOMap = getTextureFromImage(img);
currentAOMapIntensity = intensity;
}
const metalnessMap = (img) => {
currentMetalnessMap = getTextureFromImage(img);
}
const displacementMap = (img, scale = 1.0) => {
currentDisplacementScale = scale;
currentDisplacementMap = getTextureFromImage(img);
}
const bumpMap = (img, scale = 1.0) => {
currentBumpScale = scale;
currentBumpMap = getTextureFromImage(img);
}
const normalMap = (img) => {
currentNormalMap = getTextureFromImage(img);
}
const exposure = (value) => {
getRenderer().toneMappingExposure = value;
getRenderer().needsUpdate = true;
}
const getEnvironment = () => {
return scene.environment;
}
const noEnvironment = () => {
scene.environment = null;
}
/**
* Checks if a parameter is a p5.Image object
* @param {*} img - The object to check
* @returns {boolean} - true if the object is a p5.Image, otherwise false
*/
const isImage = (img) => {
return (img && img.canvas && img.canvas instanceof HTMLCanvasElement);
};
const environment = (input, attachToGrid = true, background = true, height, radius) => {
var texture;
if (!input || typeof input == "boolean") {
scene.environment = defaultEnvironment;
texture = defaultEnvironment;
background = input;
} if (isImage(input)) {
const equirectangular = getTextureFromImage(input);
texture = equirectangularToCubemap(equirectangular);
} else if (input) {
const loader = new THREE.CubeTextureLoader();
texture = input.isTexture ? input : loader.load([
`${input}px.png`,
`${input}nx.png`,
`${input}py.png`,
`${input}ny.png`,
`${input}pz.png`,
`${input}nz.png`,
]);
}
scene.environment = texture;
if (background) {
skybox(texture, attachToGrid, height, radius);
}
renderer.needsUpdate = true;
return texture;
}
var currentGroundProjectedEnv;
const getDefaultShape = (radius, sides) => {
const pts1 = [], count = sides;
for (let i = 0; i < count; i++) {
const l = radius;
const a = 2 * i / count * Math.PI;
pts1.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
}
return new THREE.Shape(pts1);
}
const beginShape2D = () => {
if (vertexContext != NONE) {
console.warn("You must use endShape2D() before using beginShape2D() again!");
return;
}
vertexContext = SHAPING;
const newShape = new Shape();
newShape.justCreated = true;
currentShape = newShape;
return newShape;
}
const endShape2D = () => {
vertexContext = NONE;
return currentShape;
}
const addVertex = (x, y, z) => {
if (vertexContext == NONE) {
console.warn("addVertex() can be used only inside beginShape2D() and endShape2D() or beginExtrude() and endExtrude() or beginLathe() and endLathe() ");
return;
}
const newPoint = new Vector3(x, y, z);
if (vertexContext == LATHING) {
currentLathe.push(newPoint)
} else if (vertexContext == EXTRUDING) {
currentExtrude.push(newPoint)
} else if (vertexContext == SHAPING) {
if (currentShape.justCreated) {
currentShape.justCreated = false;
currentShape.moveTo(x, y);
} else {
currentShape.lineTo(x, y);
}
}
}
const beginLathe = (sides = 32) => {
if (vertexContext != NONE) {
console.warn("You must use endLathe() before using beginLathe() again!");
return;
}
vertexContext = LATHING;
currentLatheSides = sides;
currentLathe = [];
}
const endLathe = () => {
if (!vertexContext == LATHING) {
console.warn("You must use beginLathe() before using endLathe(); Cannot be used inside beginShape2D() endShape2D()!");
return;
}
const geometry = new THREE.LatheGeometry(currentLathe, currentLatheSides);
vertexContext = NONE;
return addMesh(0, 0, 0, geometry);
}
const beginExtrude = (shape, sides = 32) => {
if (vertexContext != NONE) {
console.warn("You must use endExtrude() before using beginExtrude() again!");
return;
}
if (!shape) {
shape = getDefaultShape(0.5, sides);
} else if (shape && !shape.extractPoints) { //
const radius = shape;
shape = getDefaultShape(radius, sides);
} else if (shape && shape.extractPoints) {
currentExtrudeSteps = sides;
}
//console.warn("EXPERIMENTAL: extrude methods may be subject to changes!");
vertexContext = EXTRUDING;
currentExtrudeShape = shape;
currentExtrude = [];
}
/**
* Creates a 3D mesh based on a shape, with options for depth and beveling.
*
* @param {Shape} shape - The shape to use for creating the mesh.
* @param {number} x - The x position of the mesh.
* @param {number} y - The y position of the mesh.
* @param {number} z - The z position of the mesh.
* @param {number} [depth=0] - The depth of the mesh. If not specified, a 2D shape will be used.
* @param {boolean} [bevelEnabled=false] - Specifies whether the mesh should have a bevel.
* @param {number} [bevelThickness] - The thickness of the bevel. If not specified, it is calculated based on the depth.
* @param {number} [bevelSize] - The size of the bevel. If not specified, it uses the same value as `bevelThickness`.
* @param {number} [bevelOffset=0] - The offset of the bevel from the surface of the mesh.
* @param {number} [bevelSegments=6] - The number of segments used to create the bevel.
*
* @returns {Mesh3D} The mesh created from the specified shape.
*/
const shape = (shape, x, y, z, depth = 0, bevelEnabled = false, bevelThickness, bevelSize, bevelOffset = 0, bevelSegments = 6) => {
if (depth == 0) {
const geometry = new THREE.ShapeGeometry(shape);
geometry.rotateX(-Math.PI / 2);
geometry.translate(0, -depth / 2, 0);
return addMesh(x, y, z, geometry);
} else {
if (!bevelThickness)
bevelThickness = depth * 0.1;
const extrudeSettings = {
steps: 12,
depth: depth,
bevelEnabled: bevelEnabled,
bevelThickness: bevelThickness,
bevelSize: bevelSize ? bevelSize : bevelThickness,
bevelOffset: bevelOffset,
bevelSegments: bevelSegments
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);//BufferGeometryUtils.mergeVertices();
geometry.rotateX(-Math.PI / 2);
geometry.translate(0, -depth / 2, 0);
return addMesh(x, y, z, geometry);
}
}
var currentExtrudeSteps = 12; //TODO
const endExtrude = (closed = false) => {
if (!vertexContext == EXTRUDING) {
console.warn("You must use beginExtrude() before using endExtrude(); Cannot be used inside beginShape2D() endShape2D()!");
return;
}
vertexContext = NONE;
const closedSpline = new THREE.CatmullRomCurve3(currentExtrude);
closedSpline.curveType = 'catmullrom';
closedSpline.closed = closed;
const extrudeSettings = {
steps: currentExtrude.length,
bevelEnabled: false,
extrudePath: closedSpline
};
const geometry = BufferGeometryUtils.mergeVertices(new THREE.ExtrudeGeometry(currentExtrudeShape, extrudeSettings));
currentExtrude = null;
const oldAlign = currentAlign;
align('center');
const newMesh = addMesh(0, 0, 0, geometry);
align(oldAlign);
return newMesh;
}
const beginGroup = () => {
const newGroup = new Group3D();
groupStack.push(newGroup)
currentScene = groupStack[groupStack.length - 1];
return currentScene;
}
const endGroup = () => {
const groupToEnd = groupStack.pop();
if (groupStack.length > 0) {
currentScene = groupStack[groupStack.length - 1];
} else {
currentScene = scene;
}
currentScene.add(groupToEnd);
return groupToEnd;
}
const quality = (preset) => {
if (preset === FULL_HD) {
setQuality(8, 8, 8, 8, 8, 8, 8, 8);
} else if (preset === HD) {
setQuality(2, 2, 2, 2, 2, 2, 2, 2);
} else if (preset === SD) {
setQuality(1, 1, 1, 1, 1, 1, 1, 1);
} else if (preset === LD) {
setQuality(0.5, 0.5, 0.5, 0.5, 0.5, 2, 0.3, 0.5);
}
}
const flatShading = () => {
currentShading = FLAT;
}
const smoothShading = () => {
currentShading = SMOOTH;
}
const getLast = () => {
return last
}
const align = (...align) => {
currentAlign = align;
}
const alignMesh = (mesh, aligns) => {
var bbox = new THREE.Box3();
var cent = bbox.getCenter(new THREE.Vector3());
var size = bbox.getSize(new THREE.Vector3());
bbox.setFromObject(mesh);
bbox.getCenter(cent);
bbox.getSize(size);
var x = 0;
var y = 0;
var z = 0;
for (let align of aligns)
if (align == 'top') {
y += (size.y * .5);
} else if (align == 'bottom') {
y -= (size.y * .5);
} else if (align == 'left') {
x -= (size.x * .5);
} else if (align == 'right') {
x += (size.x * .5);
} else if (align == 'forward') {
z -= (size.z * .5);
} else if (align == 'backward') {
z += (size.z * .5);
}
mesh.geometry.translate(x, y, z);
}
const subdivide = (subdivisions = 1, split = true, uvSmooth = false, preserveEdges = false, flatOnly = false, maxTriangles = Infinity) => {
currentSubdivisions = {
subdivisions: min(5, max(0, floor(subdivisions))),
split: split,
uvSmooth: uvSmooth,
preserveEdges: preserveEdges,
flatOnly: flatOnly,
maxTriangles: maxTriangles
};
notify("You are using a very experimental function");
}
const addMesh = (x, y, z, geometry, addToScene = true) => {
if (!geometry)
return;
const newMesh = new Mesh3D(geometry, currentMaterial.clone());
newMesh.material.side = DoubleSide;
//if (currentNormalMap)
newMesh.material.normalMap = currentNormalMap;
newMesh.material.map = currentDiffuseMap;// ? currentDiffuseMap : null;
newMesh.material.roughnessMap = currentRoughnessMap;// ? currentRoughnessMap : null;
// if (currentRoughnessMap)
// newMesh.material.roughness = 1.0;
// if (currentAOMap) {
newMesh.material.aoMap = currentAOMap;
newMesh.material.aoMapIntensity = currentAOMapIntensity;
// }
newMesh.material.metalnessMap = currentMetalnessMap;// ? currentMetalnessMap : null;
// if (currentMetalnessMap) {
// newMesh.material.metalness = 1.0;
// }
newMesh.material.envMap = currentEnvMap;// ? currentEnvMap : null;
newMesh.material.displacementMap = currentDisplacementMap;// ? currentDisplacementMap : null;
//if (currentDisplacementMap) {
newMesh.material.displacementScale = currentDisplacementScale;
//}
newMesh.material.bumpMap = currentBumpMap ? currentBumpMap : null;
//if (currentBumpMap) {
newMesh.material.bumpScale = currentBumpScale;
//}
newMesh.material.color = currentColor;
newMesh.material.flatShading = currentShading;
newMesh.material.needsUpdate = true;
newMesh.position.set(x, y, z);
alignMesh(newMesh, currentAlign);
if (addToScene)
currentScene.add(newMesh);
if (currentSubdivisions.subdivisions > 0) {
newMesh.subdivide(currentSubdivisions.subdivisions, currentSubdivisions.split, currentSubdivisions.uvSmooth, currentSubdivisions.preserveEdges, currentSubdivisions.flatOnly, currentSubdivisions.maxTriangles)
}
last = newMesh;
return newMesh;
}
/**
* Remove one or more Mesh3D or Group3D.
*
* @param {...Mesh3D|Group3D} objects3D meshes or groups you want to remove
*/
const removeObjects = (...objects3D) => {
for (let o of objects3D) {
if (o.parent)
o.parent.remove(o);
}
}
/**
* Set the emissive color of your next 3D objects
*
* @param {Color} color - A color rapresentation like 255, 0, 255 or "#ff00ff"
*/
const emissive = (color) => {
currentMaterial.emissive = color3D(color);
};
/**
* Set the emissive intesity of your next 3D object
*
* @param {number} intensity - A value between 0.0 and 1.0
*/
const emissiveIntensity = (intensity = 1.0) => {
currentMaterial.emissiveIntensity = intensity;
};
/**
* Set the roughness of your next 3D object
*
* @param {number} roughness - A value between 0.0 and 1.0
*/
const roughness = (roughness = 1.0) => {
currentMaterial.roughness = roughness;
}
/**
* Set the opacity of your next 3D object
*
* @param {number} opacity - A value between 0.0 and 1.0
*/
const opacity = (opacity = 1.0) => {
currentMaterial.opacity = opacity;
if (currentMaterial.opacity < 1)
currentMaterial.transparent = true;
else
currentMaterial.transparent = false;
}
/**
* Set the metalness of your next 3D object
*
* @param {number} metalness - A value between 0.0 and 1.0
*/
const metalness = (metalness = 0.0) => {
currentMaterial.metalness = metalness;
}
/**
* Get active metalness value
* @returns {number} Current material metalness value between 0.0 and 1.0
*/
const getMetalness = () => {
return currentMaterial.metalness;
}
/**
* Get active opacity value
* @returns {number} Current material opacity value between 0.0 and 1.0
*/
const getOpacity = () => {
return currentMaterial.opacity;
}
/**
* Get active roughness value
* @returns {number} Current material roughness value between 0.0 and 1.0
*/
const getRoughness = () => {
return currentMaterial.roughness;
}
/**
* Get active emissive color
* @returns {Color} - Current material emissive color
*/
const getEmissive = () => {
return currentMaterial.emissive.clone();
}
/**
* Get active emissiveIntensity value
* @returns {number} Current material emissiveIntensity value between 0.0 and 1.0
*/
const getEmissiveIntensity = () => {
return currentMaterial.emissiveIntensity;
}
/**
* Get a copy of active material
* @returns {Material} A copy of current material
*/
const getMaterial = () => {
const material = currentMaterial.clone();
material.color = currentColor.clone();
material.flatShading = currentShading;
return material;
}
/**
*
* Quickest way of setting a material.
*
* @param {Color|Material} material This can be a color or another material
* @param {number} roughness Optional roughness. Default 1.0
* @param {number} opacity Optional opacity. Default 1.0
* @param {number} metalness Optional metalness. Default 0.0
* @param {Color} emissive Optional emissive color. Default is black (no emissive color)
* @param {number} emissiveIntensity Optional emissiveIntensity. Default 1.0
* @returns {Material} newly created material
*/
const material = (...params) => {
if (params.length == 0)
return getMaterial();
currentMaterial = new MeshStandardMaterial();
if (params.length == 1) {
if (params[0].isMeshStandardMaterial) {
currentMaterial = params[0].clone();
currentColor = params[0].color.clone();
currentShading = params[0].flatShading;
} else {
fill(params[0]);
}
}
if (params.length > 1) {
fill(params[0]);
roughness(params[1]);
}
if (params.length > 2)
opacity(params[2]);
if (params.length > 3)
metalness(params[3]);
if (params.length > 4)
emissive(params[4]);
if (params.length > 5)
emissiveIntensity(params[5]);
return getMaterial();
}
/**
* Add a clone of a Mesh3D or a Group to new position.
*
* Cloning a Mesh3D will take into account current material in use. For example fill("red") before cloning
* would change the color of the cloned Mesh3D.
*
* Group3D objects maintain their original material.
*
* @param {Mesh3D|Group3D} object Object or Group you want to clone
* @param {number} x Default 0.0
* @param {number} y Default 0.0
* @param {number} z Default 0.0
* @returns {Mesh3D|Group3D} cloned Object or Group depending on input
*/
const clone = (object, x = 0, y = 0, z = 0) => {
if (object.isGroup || object.isMesh) {
const clonedObject = SkeletonUtils.clone(object);
clonedObject.position.set(x, y, z);
currentScene.add(clonedObject);
return clonedObject
} else {
notify("clone() works only with groups or mesh!", REASON_CANNOT);
return null;
}
}
const getCenteredMesh = (result) => {
const oldAlign = currentAlign;
align('center');
const newMesh = addMesh(result.position.x, result.position.y, result.position.z, result.geometry);
align(oldAlign);
return newMesh
}
/**
*
* Create a new object subtracting a from b.
*
* @param {Mesh3D} a Source object
* @param {Mesh3D} b Object you want to subtract from source
* @param {boolean} removeOriginals Use false if you want to mantain original object in the scene. Default true
* @returns {Mesh3D} newly created object
*/
const subtract = (source, ...targets) => {
var result = source;
removeObjects(source)
for (let target of targets) {
result = booleanOperation(result, target, 'subtract');
removeObjects(target);
}
return getCenteredMesh(result);
}
/**
*
* Create a new object merging a and b togheter.
*
* @param {Mesh3D} a Source object
* @param {Mesh3D} b Object you want to merge to source
* @param {boolean} removeOriginals Use false if you want to mantain original object in the scene. Default true
* @returns {Mesh3D} newly created object
*/
const union = (source, ...targets) => {
var result = source;
removeObjects(source)
for (let target of targets) {
result = booleanOperation(result, target, 'union');
removeObjects(target);
}
return getCenteredMesh(result);
}
/**
*
* Create a new object from the intersection between a and b.
*
* @param {Mesh3D} a Source object
* @param {Mesh3D} b Object you want to intersect with source
* @param {boolean} removeOriginals Use false if you want to mantain original object in the scene. Default true
* @returns {Mesh3D} newly created object
*/
const intersect = (source, ...targets) => {
var result = source;
removeObjects(source)
for (let target of targets) {
result = booleanOperation(result, target, 'intersect');
removeObjects(target);
}
return getCenteredMesh(result);
}
/**
* Create a plane with an image as texture
* @param {*} img Texture image (Remember to load your images inside preload())
* @param {*} x Position x
* @param {*} y Position y
* @param {*} z Position z
* @param {*} width Width of the plane. You may want to use a multiple of img.width to avoid stretching. Default 1.0
* @param {*} height Height of the plane. You may want to use a multiple of img.height to avoid stretching. Default 1.0
* @returns {Mesh3D} 3D plane with the image as texture
*/
const image3D = (img, x, y, z, width, height) => {
const map = getTextureFromImage(img);
const aspectRatio = img.width / img.height;
if (!height) {
width = width ? width : 1;
height = width / aspectRatio;
//console.log(aspectRatio)
}
const p = plane(x, y, z, width, height);
p.material.map = map;
p.material.depthWrite = false;
p.material.transparent = true;
p.material.alphaTest = 0.01;
//p.material.depthTest = false;
p.material.needsUpdate = true;
return p;
}
/**
* Add a sphere to the scene.
*
* @param {number} x X position
* @param {number} y Y position
* @param {number} z Z position
* @param {number} radius Optional radius of the sphere. Default 0.5
* @param {number} phiStart
* @param {number} phiLength
* @param {number} thetaStart
* @param {number} thetaLength
* @returns {Mesh3D} newly created sphere object
*/
const sphere = (x = 0, y = 0, z = 0, radius = 0.5, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI) => {
if (radius == 0) {
notify("You tried to create a sphere with radius = 0. No sphere was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'sphere', radius: radius, phiStart: phiStart, phiLength: phiLength, thetaStart: thetaStart, thetaLength: thetaLength });
return addMesh(x, y, z, geometry);
}
const circle = (x = 0, y = 0, z = 0, radius = 0.5) => {
const geometry = getGeometry({ type: 'circle', radius: radius });
return addMesh(x, y, z, geometry);
}
const ring = (x = 0, y = 0, z = 0, outerRadius = 0.5, innerRadius = 0.2) => {
const geometry = getGeometry({ type: 'ring', outerRadius: outerRadius, innerRadius: innerRadius });
return addMesh(x, y, z, geometry);
}
/**
* Add a cone to the scene.
*
* @param {number} x X position
* @param {number} y Y position
* @param {number} z Z position
* @param {number} radius Optional radius of the cone. Default 0.5
* @param {number} height Optional height of the cone. Default 1
* @returns {Mesh3D} newly created cone object
*/
const cone = (x = 0, y = 0, z = 0, radius = 0.5, height = 1) => {
if (radius == 0) {
notify("You tried to create a cone with radius = 0. No cone was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'cone', radius: radius, height: height });
return addMesh(x, y, z, geometry);
}
/**
* Add a capsule to the scene.
*
* @param {number} x X position
* @param {number} y Y position
* @param {number} z Z position
* @param {number} radius Optional radius of the capsule. Default 0.5
* @param {number} length Optional length of the capsule. Default 1
* @param {number} capSubdivisions Optional number of cap subdivisions of the capsule. Default 32
* @param {number} radialSegments Optional number of radial segments of the capsule. Default 12
* @returns {Mesh3D} newly created cone object
*/
const capsule = (x = 0, y = 0, z = 0, radius = 0.5, length = 1, capSubdivisions, radialSegments) => {
if (radius == 0) {
notify("You tried to create a capsule with radius = 0. No capsule was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'capsule', radius: radius, length: length, radialSegments: radialSegments, capSubdivisions: capSubdivisions });
return addMesh(x, y, z, geometry);
}
/**
*
* @param {number} x X position
* @param {number} y Y position
* @param {number} z Z position
* @param {number} radiusTop Optional radius of the cylinder. Default 0.5
* @param {number} height Optional height of the cylinder. Default 1
* @param {number} radialSegments Optional number of radial segments of the cylinder. Default 32
* @param {number} radiusBottom Optional bottom radius of the cylinder. Default null (egual to radiusTop)
* @param {number} heightSegments Optional number of height segments of the cylinder. Default 12
* @returns {Mesh3D} newly created object
*/
const cylinder = (x = 0, y = 0, z = 0, radiusTop = 0.5, height = 1, radialSegments, radiusBottom, heightSegments) => {
if (radiusTop == 0) {
notify("You tried to create a cylinder with radius = 0. No cylinder was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'cylinder', radiusTop: radiusTop, radiusBottom: radiusBottom ? radiusBottom : radiusTop, height: height, radialSegments: radialSegments, heightSegments: heightSegments });
return addMesh(x, y, z, geometry);
}
/**
* Create a cube object.
* @param {number} x X position
* @param {number} y Y position
* @param {number} z Z position
* @param {number} side Side length
* @returns {Mesh3D} newly created object
*/
const cube = (x, y, z, side) => {
return box(x, y, z, side, side, side);
}
const plane = (x = 0, y = 0, z = 0, width = 1, height = 1, widthSegments = 1, heightSegments = 1) => {
const geometry = getGeometry({ type: 'plane', width: width, height: height, widthSegments: widthSegments, heightSegments: heightSegments });
return addMesh(x, y, z, geometry);
}
/**
* Create a box object.
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} width
* @param {number} height
* @param {number} depth
* @returns {Mesh3D} newly created object
*/
const box = (x = 0, y = 0, z = 0, width = 1, height = 1, depth = 1) => {
const geometry = getGeometry({ type: 'box', width: width, height: height, depth: depth });
return addMesh(x, y, z, geometry);
}
/**
*
* Create an ellipsoid object. It's like a box buf spherical ;-)
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} width
* @param {number} height
* @param {number} depth
* @returns {Mesh3D} newly created object
*/
const ellipsoid = (x = 0, y = 0, z = 0, width = 1, height = 1, depth = 1) => {
const geometry = getGeometry({ type: 'ellipsoid', width: width, height: height, depth: depth });
return addMesh(x, y, z, geometry);
}
/**
* Create a torus (like a donut)
*
* @param {number} [x=0] - The x coordinate of the torus center.
* @param {number} [y=0] - The y coordinate of the torus center.
* @param {number} [z=0] - The z coordinate of the torus center.
* @param {number} [radius=0.5] - The radius of the torus.
* @param {number} [tubeRadius=0.2] - The radius of the tube of the torus.
* @returns {Mesh3D} The newly created torus mesh object.
*/
const torus = (x = 0, y = 0, z = 0, radius = 0.5, tubeRadius = 0.2) => {
if (radius == 0) {
notify("You tried to create a torus with radius = 0. No torus was created.", REASON_CHECK);
return
}
if (tubeRadius == 0) {
notify("You tried to create a torus with tubeRadius = 0. No torus was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'torus', radius: radius, tube: tubeRadius });
return addMesh(x, y, z, geometry);
}
/**
*
* Create an icosahedron. Can be used to create spheres with more regular topology than the sphere()
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} radius
* @param {number} detail Default is 0. More then 1 it's a sphere
* @returns {Mesh3D} newly created object
*/
const icosahedron = (x = 0, y = 0, z = 0, radius = 0.5, detail = 0) => {
if (radius == 0) {
notify("You tried to create a icosahedron with radius = 0. No icosahedron was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'icosahedron', radius: radius, detail: detail });
return addMesh(x, y, z, geometry);
}
/**
* Create an octahedron.
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} radius
* @param {number} detail
* @returns {Mesh3D} newly created object
*/
const octahedron = (x = 0, y = 0, z = 0, radius = 0.5, detail = 0) => {
if (radius == 0) {
notify("You tried to create a octahedron with radius = 0. No octahedron was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'octahedron', radius: radius, detail: detail });
return addMesh(x, y, z, geometry);
}
/**
* Create an tetrahedron.
*
* @param {number} x
* @param {number} y
* @param {number} z
* @param {number} radius
* @param {number} detail
* @returns {Mesh3D} newly created object
*/
const tetrahedron = (x = 0, y = 0, z = 0, radius = 0.5, detail = 0) => {
if (radius == 0) {
notify("You tried to create a shape with radius = 0. No shape was created.", REASON_CHECK);
return
}
const geometry = getGeometry({ type: 'tetrahedron', radius: radius, detail: detail });
return addMesh(x, y, z, geometry);
}
const pyramid = (x = 0, y = 0, z = 0, width = 1, height = 1, depth = 1) => {
const geometry = getGeometry({ type: 'pyramid', width: width, height: height, depth: depth });
return addMesh(x, y - (height * 0.5), z, geometry);
}
const inspect = (something) => {
if (inspector) {
inspector.inspect(something);
}
}
const lookAt = (target, y, z) => {
if (target == undefined) {
target = new Vector3();
}
if (target.isVector3)
orbit.target = target.clone();
else if (target.isObject3D) {
if (target.parent != scene) {
target.traverseAncestors((o) => {
if (o.parent == scene) {
target = o;
}
})
}
orbit.target = target.position.clone();
}
else if (target != undefined && y != undefined && z != undefined) {
orbit.target = new Vector3();
orbit.target.set(target, y, z);
}
orbit.update();
}
/**
*
* Use pickObject(x,y) to know which object is under x,y coords (pixel) of the view.
* You can use mouseX, mouseY to evaluate the object under mouse cursor.
*
*
* @param {Integer} x
* @param {Integer} y
* @returns Object is under x,y coords (pixel) of the view. null if there is no object rendered at those coords
* @example
* // Change the color of an object when clicked
* function mousePressed() {
* var clickedObject = pickObject(mouseX, mouseY);
*
* if (clickedObject) {
* //implement what happen if you clicked on something
* clickedObject.setColor("red")
* } else {
* //no object under the mouse
* }
* }
*/
const pickObject = (x, y) => {
if (!x && !y && p5 && p5.instance) {
x = p5.instance.mouseX;
y = p5.instance.mouseY;
}
const currentObjectUnderMouse = scene.getObjectById(picker.pick(x - p5.instance.canvas.getBoundingClientRect().left, y - p5.instance.canvas.getBoundingClientRect().top));
if (currentObjectUnderMouse && currentObjectUnderMouse.isMesh)
return currentObjectUnderMouse;
}
const showToolbox = () => {
toolboxContainer.style.display = 'initial'
}
const hideToolbox = () => {
toolboxContainer.style.display = 'none'
}
/**
*
* Use showGrid() to have the reference floor grid shown in your 3D scene. Use hideGrid() to hide it.
*
* @param {Boolean} withGrid If specified it will show the grid with true and hide the grid with false. Default true
*/
const showGrid = (withGrid = true) => {
scene.showGrid(withGrid);
}
/**
* Hide the reference floor grid in your scene. Use showGrid() to enable it again.
*/
const hideGrid = () => {
scene.showGrid(false);
}
const getScene = () => {
return scene;
}
const getCamera = () => {
return cam;
}
const setCamera = (...params) => {
if (params.length >= 1 && params.length < 3) {
cam.position.set(params[0].x, params[0].y, params[0].z);
if (params.length == 2)
orbit.target.set(params[1].x, params[1].y, params[1].z);
} else if (params.length >= 3 && !Number.isNaN(params[0]) && !Number.isNaN(params[1]) && !Number.isNaN(params[2])) {
cam.position.set(params[0], params[1], params[2]);
if (params.length == 6 && !Number.isNaN(params[3]) && !Number.isNaN(params[4]) && !Number.isNaN(params[5])) {
orbit.target.set(params[3], params[4], params[5]);
}
}
cam.updateProjectionMatrix();
orbit.update();
}
const changeCamera = (type = PERSPECTIVE, ...params) => {
if (type == PERSPECTIVE) {
cam == new PerspectiveCamera(
75,
p5.instance.width / p5.instance.height, //aspect
0.1,
1000
);
setCamera(CAMERA_DEFAULT_POSITION_PERSPECTIVE)
} else if (type == ORTHOGRAPHIC) {
cam = new THREE.OrthographicCamera(p5.instance.width / - 2, p5.instance.width / 2, p5.instance.height / 2, p5.instance.height / - 2, 0, 1000);
setCamera(CAMERA_DEFAULT_POSITION_ORTHOGRAPHIC);
fitZoom();
}
cam.updateProjectionMatrix();
orbit = new OrbitControls(cam, p5.instance.canvas);
orbit.update();
return cam;
}
const getRenderer = () => {
return renderer;
}
const setZoom = (zoom = 1) => {
cam.zoom = zoom;
cam.updateProjectionMatrix();
orbit.update();
}
const fitZoom = (selection) => {
const size = new THREE.Vector3();
const center = new THREE.Vector3();
const box = new THREE.Box3();
if (!selection)
selection = getScene().children;
//For centering the meshGroup
box.makeEmpty();
if (selection.isGroup) {
selection = selection.children;
} else if (selection.isMesh) {
selection = [selection]
}
for (const object of selection) {
if (object.visible)
box.expandByObject(object);
}
box.getSize(size);
box.getCenter(center);
//box.center(this.scene);
//meshGroup.localToWorld(box);
//meshGroup.position.multiplyScalar(-1);
//For fitting the object to the screen when using orthographic camera
const currentCamera = getCamera();
if (currentCamera.isOrthographicCamera) {
const fitOffset = 0.7;
currentCamera.zoom = Math.min(width / (box.max.x - box.min.x),
height / (box.max.y - box.min.y)) * fitOffset;
var cameraPosition = CAMERA_DEFAULT_POSITION_ORTHOGRAPHIC.clone();
currentCamera.position.set(currentCamera.position.x + center.x, currentCamera.position.y + center.y, currentCamera.position.z + center.z);
orbit.target.set(center.x, center.y, center.z)
currentCamera.updateProjectionMatrix();
currentCamera.updateMatrix();
} else if (currentCamera.isPerspectiveCamera) {
const fitOffset = 1.2;
const maxSize = Math.max(size.x, size.y, size.z);
const fitHeightDistance = maxSize / (2 * Math.atan(Math.PI * currentCamera.fov / 360));
const fitWidthDistance = fitHeightDistance / currentCamera.aspect;
const distance = fitOffset * Math.max(fitHeightDistance, fitWidthDistance);
const direction = orbit.target.clone()
.sub(currentCamera.position)
.normalize()
.multiplyScalar(distance);
orbit.maxDistance = distance * 10;
orbit.target.copy(center);
currentCamera.near = distance / 100;
currentCamera.far = distance * 100;
currentCamera.updateProjectionMatrix();
currentCamera.position.copy(orbit.target).sub(direction);
orbit.update();
}
}
const getZoom = () => {
return cam.zoom
}
const createGradient = (colors, steps) => {
let gradient = [];
for (let i = 0; i < colors.length - 1; i++) {
let startColor = color(colors[i]);
let endColor = color(colors[i + 1]);
for (let j = 0; j < steps; j++) {
let interp = j / (steps - 1);
gradient.push(lerpColor(startColor, endColor, interp));
}
}
return gradient;
}
const VERTICAL = 'vertical';
const HORIZONTAL = 'horizontal';
const TILES = 'tiles';
const getGradient = (mode, steps, ...colors) => {
notify("You are using a very experimental function");
//const steps = 64;
steps = steps ? floor(steps) : 32;
const gradient = createGradient(colors, steps);
const tempG = createGraphics(512, 512);
tempG.ellipseMode(CORNER);
tempG.background("magenta");
tempG.noStroke();
if (mode == VERTICAL) {
for (let row = 0; row < tempG.height; row += tempG.height / steps) {
tempG.fill(gradient[floor(map(row, 0, tempG.height, 0, gradient.length))])
tempG.rect(0, row, tempG.width, floor(tempG.height / steps) + 2);
}
} else if (mode == HORIZONTAL) {
for (let col = 0; col < tempG.width; col += tempG.width / steps) {
tempG.fill(gradient[floor(map(col, 0, tempG.width, 0, gradient.length))])
tempG.rect(col, 0, floor(tempG.width / steps) + 2, tempG.height);
}
} else if (mode == TILES) {
for (let row = 0; row < tempG.height; row += tempG.height / steps)
for (let col = 0; col < tempG.width; col += tempG.width / steps) {
tempG.fill(gradient[floor(map(random() * tempG.width, 0, tempG.width, 0, gradient.length))])
tempG.rect(col, row, floor(tempG.width / steps) + 2, floor(tempG.height / steps) + 2);
}
}
//img.copy(tempG, 0, 0, tempG.width, tempG.height, 0, 0, img.width, img.height)
return tempG;
}
p5.prototype.getGradient = getGradient;
p5.prototype.VERTICAL = VERTICAL;
p5.prototype.HORIZONTAL = HORIZONTAL;
p5.prototype.TILES = TILES;
p5.prototype.THREE = THREE;
p5.prototype.getScene = getScene;
p5.prototype.getCamera = getCamera;
p5.prototype.setZoom = setZoom;
p5.prototype.getZoom = getZoom;
p5.prototype.fitZoom = fitZoom;
p5.prototype.showToolbox = showToolbox;
p5.prototype.hideToolbox = hideToolbox;
p5.prototype.getOrbit = getOrbit;
p5.prototype.getRenderer = getRenderer;
p5.prototype.resizeCanvas3D = resizeCanvas3D;
p5.prototype.onScreen = onScreen;
p5.prototype.showGrid = showGrid;
p5.prototype.hideGrid = hideGrid;
p5.prototype.addMesh = addMesh;
p5.prototype.clone = clone;
p5.prototype.removeFromScene = removeObjects;
p5.prototype.removeObjects = removeObjects;
p5.prototype.removeObject = removeObjects;
p5.prototype.getLast = getLast;
p5.prototype.text3D = text3D;
p5.prototype.setFont3D = setFont3D;
p5.prototype.beginGroup = beginGroup;
p5.prototype.endGroup = endGroup;
p5.prototype.addVertex = addVertex;
p5.prototype.beginShape2D = beginShape2D;
p5.prototype.endShape2D = endShape2D;
p5.prototype.beginExtrude = beginExtrude;
p5.prototype.endExtrude = endExtrude;
p5.prototype.beginLathe = beginLathe;
p5.prototype.endLathe = endLathe;
p5.prototype.normalMap = normalMap;
p5.prototype.diffuseMap = diffuseMap;
p5.prototype.roughnessMap = roughnessMap;
p5.prototype.ambientOcclusionMap = ambientOcclusionMap;
p5.prototype.metalnessMap = metalnessMap;
p5.prototype.displacementMap = displacementMap;
p5.prototype.bumpMap = bumpMap;
p5.prototype.envMap = envMap;
p5.prototype.noNormalMap = noNormalMap;
p5.prototype.noDiffuseMap = noDiffuseMap;
p5.prototype.noRoughnessMap = noRoughnessMap;
p5.prototype.noMetalnessMap = noMetalnessMap;
p5.prototype.noDisplacementMap = noDisplacementMap;
p5.prototype.noBumpMap = noBumpMap;
p5.prototype.noEnvMap = noEnvMap;
p5.prototype.pointLight = pointLight; //Overwriten
p5.prototype.ambientLight = ambientLight; //Overwriten
p5.prototype.environment = environment;
p5.prototype.noEnvironment = noEnvironment;
p5.prototype.getEnvironment = getEnvironment;
p5.prototype.getPresets = getPresets;
p5.prototype.exposure = exposure;
p5.prototype.align = align;
p5.prototype.FORWARD = FORWARD;
p5.prototype.BACKWARD = BACKWARD;
p5.prototype.shape = shape;
p5.prototype.image3D = image3D;
p5.prototype.loadFont3D = loadFont3D;
p5.prototype.box = box; //Overwriten
p5.prototype.cube = cube;
p5.prototype.plane = plane; //Overwriten
//p5.prototype.circle = circle;
p5.prototype.ring = ring;
p5.prototype.sphere = sphere; //Overwriten
p5.prototype.ellipsoid = ellipsoid; //Overwriten
p5.prototype.cone = cone; //Overwriten
p5.prototype.capsule = capsule;
p5.prototype.cylinder = cylinder; //Overwriten
p5.prototype.torus = torus; //Overwriten
p5.prototype.icosahedron = icosahedron;
p5.prototype.octahedron = octahedron;
p5.prototype.tetrahedron = tetrahedron;
p5.prototype.pyramid = pyramid;
p5.prototype.smoothShading = smoothShading;
p5.prototype.flatShading = flatShading;
p5.prototype.quality = quality;
p5.prototype.setQuality = setQuality;
p5.prototype.FULL_HD = FULL_HD;
p5.prototype.HD = HD;
p5.prototype.SD = SD;
p5.prototype.LD = LD;
//p5.prototype.color = color;
p5.prototype.randomColor = randomColor;
// p5.prototype.hue = hue;
// p5.prototype.saturation = saturation;
// p5.prototype.lightness = lightness;
p5.prototype.background3D = background3D;
p5.prototype.diffuse = diffuse;
p5.prototype.getDiffuse = getDiffuse;
p5.prototype.inspect = inspect;
p5.prototype.subtract = subtract;
p5.prototype.union = union;
p5.prototype.intersect = intersect;
p5.prototype.material = material;
p5.prototype.emissive = emissive;
p5.prototype.emissiveIntensity = emissiveIntensity;
p5.prototype.roughness = roughness;
p5.prototype.opacity = opacity;
p5.prototype.metalness = metalness;
p5.prototype.getEmissiveIntensity = getEmissiveIntensity;
p5.prototype.getEmissive = getEmissive;
p5.prototype.getRoughness = getRoughness;
p5.prototype.getOpacity = getOpacity;
p5.prototype.getMetalness = getMetalness;
p5.prototype.Mesh3D = Mesh3D;
p5.prototype.Group3D = Group3D;
//p5.prototype.Color = Color;
p5.prototype.VertexNormalsHelper = VertexNormalsHelper;
p5.prototype.object3D = null;
p5.prototype.createCanvas3D = createCanvas3D;
p5.prototype.render3D = render3D;
p5.prototype.lookAt = lookAt;
p5.prototype.setCamera = setCamera;
p5.prototype.changeCamera = changeCamera;
p5.prototype.pickObject = pickObject;
p5.prototype.pushFX = pushFX;
p5.prototype.popFX = popFX;
p5.prototype.getFX = getFX;
p5.prototype.removeFX = removeFX;
p5.prototype.clearFX = clearFX;
p5.prototype.anaglyph = anaglyph;
p5.prototype.GLITCH = GLITCH;
p5.prototype.LUMINOSITY = LUMINOSITY;
p5.prototype.SOBEL = SOBEL;
p5.prototype.BLOOM = BLOOM;
p5.prototype.PIXEL = PIXEL;
p5.prototype.HALFTONE = HALFTONE;
//p5.prototype.DEPTH = DEPTH;
p5.prototype.HALFTONE_DOT = HALFTONE_DOT;
p5.prototype.HALFTONE_ELLIPSE = HALFTONE_ELLIPSE;
p5.prototype.HALFTONE_LINE = HALFTONE_LINE;
p5.prototype.HALFTONE_SQUARE = HALFTONE_SQUARE;
p5.prototype.ORTHOGRAPHIC = ORTHOGRAPHIC;
p5.prototype.PERSPECTIVE = PERSPECTIVE;
p5.prototype.CINEMA = CINEMA;
p5.prototype.ROTUNDA = ROTUNDA;
p5.prototype.SUNSET = SUNSET;
p5.prototype.SWITZERLAND = SWITZERLAND;
p5.prototype.NIGHT = NIGHT;
p5.prototype.NEON = NEON;
p5.prototype.VERANDA = VERANDA;
p5.prototype.STARS = STARS;
p5.prototype.FOREST = FOREST;
p5.prototype.UBUNTU = UBUNTU;
p5.prototype.loadSheet = loadSheet;
p5.prototype.loadAsset = loadAsset;
p5.prototype.loadObject = loadObject;
p5.prototype.subdivide = subdivide;
p5.prototype.skybox = skybox;
p5.prototype.noSkybox = noSkybox;
p5.prototype.FirstPersonControls = FirstPersonControls;
p5.prototype.registerMethod("init", init3D);
p5.prototype.registerMethod("pre", render3D);
p5.prototype.registerPreloadMethod('loadFont3D', p5.prototype);
p5.prototype.registerPreloadMethod("loadAsset", p5.prototype);
p5.prototype.registerPreloadMethod("loadObject", p5.prototype);
p5.prototype.registerPreloadMethod("loadSheet", p5.prototype);