1.2K tris0.21 × 0.22 × 0.22 m
Use it in code
Remix in the editorcandle.js · 194 lines
Self-contained ES module — only depends on three. Call the factory and add the group to your scene.
/**
* Candles — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 1,150 · Size (W × H × D): 0.21 × 0.22 × 0.22 m
*
* Usage:
* import { createCandle } from './candle.js';
*
* const model = createCandle();
* model.position.set(0, 0, 0);
* scene.add(model);
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Deterministic vertex displacement (organic low-poly look).
function jitter(geometry, amount, seed) {
const pos = geometry.attributes.position;
const h = (a, b, c, s) => {
let x = Math.imul(a, 73856093) ^ Math.imul(b, 19349663) ^ Math.imul(c, 83492791) ^ Math.imul(s, 2654435761);
x = Math.imul(x ^ (x >>> 16), 2246822507);
x = Math.imul(x ^ (x >>> 13), 3266489909);
return ((x ^ (x >>> 16)) >>> 0) / 4294967295 - 0.5;
};
for (let i = 0; i < pos.count; i++) {
const x = pos.getX(i), y = pos.getY(i), z = pos.getZ(i);
const qx = Math.round(x * 1000), qy = Math.round(y * 1000), qz = Math.round(z * 1000);
pos.setXYZ(i, x + h(qx, qy, qz, seed) * 2 * amount, y + h(qx, qy, qz, seed + 1) * 2 * amount, z + h(qx, qy, qz, seed + 2) * 2 * amount);
}
pos.needsUpdate = true;
geometry.computeVertexNormals();
return geometry;
}
// Revolve a 2D profile [[radius, y], ...] around the Y axis.
function lathe(points, segments, phiStart = 0, phiLength = Math.PI * 2) {
return new THREE.LatheGeometry(points.map(([x, y]) => new THREE.Vector2(x, y)), segments, phiStart, phiLength);
}
export function createCandle() {
const root = new THREE.Group();
root.name = 'Candle';
// Materials
const waxMat = new THREE.MeshStandardMaterial({ name: 'wax', color: 0xf1dfb8, roughness: 0.55, flatShading: true });
const dripMat = new THREE.MeshStandardMaterial({ name: 'drip', color: 0xfbe8bf, roughness: 0.45, flatShading: true });
const dishMat = new THREE.MeshStandardMaterial({ name: 'dish', color: 0xc08a3e, roughness: 0.35, metalness: 0.55, flatShading: true });
const wickMat = new THREE.MeshStandardMaterial({ name: 'wick', color: 0x2a2420, roughness: 1, flatShading: true });
const flameOutMat = new THREE.MeshStandardMaterial({ name: 'flameOut', color: 0xff9a2a, roughness: 1, emissive: 0xff7a10, emissiveIntensity: 1.4, flatShading: true });
const flameInMat = new THREE.MeshStandardMaterial({ name: 'flameIn', color: 0xfff1a8, roughness: 1, emissive: 0xffd860, emissiveIntensity: 1.8, flatShading: true });
// Shared geometries
const cylinderGeo = new THREE.CylinderGeometry(0.0018, 0.002, 0.014, 4);
const sphereGeo = new THREE.SphereGeometry(0.0095, 7, 5);
const coneGeo = new THREE.ConeGeometry(0.0094, 0.034, 7);
// Parts
const dish = new THREE.Mesh(lathe([[0, 0], [0.085, 0], [0.096, 0.006], [0.108, 0.019], [0.104, 0.022], [0.088, 0.012], [0, 0.012]], 14), dishMat);
dish.name = 'dish';
root.add(dish);
const candle1 = new THREE.Group();
candle1.name = 'candle1';
candle1.position.set(-0.03, 0, -0.028);
root.add(candle1);
const waxPool = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0348, 0.0408, 0.006, 9), 0.0015, 1), dripMat);
waxPool.name = 'waxPool';
waxPool.position.set(0, 0.014, 0);
candle1.add(waxPool);
const body = new THREE.Mesh(new THREE.CylinderGeometry(0.024, 0.025, 0.16, 9), waxMat);
body.name = 'body';
body.position.set(0, 0.092, 0);
candle1.add(body);
const rim = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0254, 0.0245, 0.01, 9), 0.0018, 4), dripMat);
rim.name = 'rim';
rim.position.set(0, 0.171, 0);
candle1.add(rim);
const drip = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0441, 2, 5), dripMat);
drip.name = 'drip';
drip.position.set(-0.0151, 0.1459, -0.0181);
candle1.add(drip);
const drip2 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0402, 2, 5), dripMat);
drip2.name = 'drip';
drip2.position.set(0.0232, 0.1479, 0.0039);
candle1.add(drip2);
const drip3 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0423, 2, 5), dripMat);
drip3.name = 'drip';
drip3.position.set(-0.0135, 0.1468, 0.0192);
candle1.add(drip3);
const wick = new THREE.Mesh(cylinderGeo, wickMat);
wick.name = 'wick';
wick.position.set(0, 0.179, 0);
candle1.add(wick);
const flameBase = new THREE.Mesh(sphereGeo, flameOutMat);
flameBase.name = 'flameBase';
flameBase.position.set(0, 0.188, 0);
flameBase.scale.set(1, 1.15, 1);
candle1.add(flameBase);
const flameTip = new THREE.Mesh(coneGeo, flameInMat);
flameTip.name = 'flameTip';
flameTip.position.set(0, 0.205, 0);
candle1.add(flameTip);
const candle2 = new THREE.Group();
candle2.name = 'candle2';
candle2.position.set(0.042, 0, -0.012);
candle2.rotation.set(0, deg(70), 0);
root.add(candle2);
const waxPool2 = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0305, 0.0357, 0.006, 9), 0.0015, 2), dripMat);
waxPool2.name = 'waxPool';
waxPool2.position.set(0, 0.014, 0);
candle2.add(waxPool2);
const body2 = new THREE.Mesh(new THREE.CylinderGeometry(0.021, 0.0218, 0.115, 9), waxMat);
body2.name = 'body';
body2.position.set(0, 0.0695, 0);
candle2.add(body2);
const rim2 = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0223, 0.0214, 0.01, 9), 0.0018, 5), dripMat);
rim2.name = 'rim';
rim2.position.set(0, 0.126, 0);
candle2.add(rim2);
const drip4 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0233, 2, 5), dripMat);
drip4.name = 'drip';
drip4.position.set(-0.0129, 0.1113, -0.0161);
candle2.add(drip4);
const drip5 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0446, 2, 5), dripMat);
drip5.name = 'drip';
drip5.position.set(0.0171, 0.1007, 0.0114);
candle2.add(drip5);
const wick2 = new THREE.Mesh(cylinderGeo, wickMat);
wick2.name = 'wick';
wick2.position.set(0, 0.134, 0);
candle2.add(wick2);
const flameBase2 = new THREE.Mesh(sphereGeo, flameOutMat);
flameBase2.name = 'flameBase';
flameBase2.position.set(0, 0.143, 0);
flameBase2.scale.set(1, 1.15, 1);
candle2.add(flameBase2);
const flameTip2 = new THREE.Mesh(coneGeo, flameInMat);
flameTip2.name = 'flameTip';
flameTip2.position.set(0, 0.16, 0);
candle2.add(flameTip2);
const candle3 = new THREE.Group();
candle3.name = 'candle3';
candle3.position.set(0, 0, 0.042);
candle3.rotation.set(0, deg(140), 0);
root.add(candle3);
const waxPool3 = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0276, 0.0323, 0.006, 9), 0.0015, 3), dripMat);
waxPool3.name = 'waxPool';
waxPool3.position.set(0, 0.014, 0);
candle3.add(waxPool3);
const body3 = new THREE.Mesh(new THREE.CylinderGeometry(0.019, 0.0198, 0.075, 9), waxMat);
body3.name = 'body';
body3.position.set(0, 0.0495, 0);
candle3.add(body3);
const rim3 = new THREE.Mesh(jitter(new THREE.CylinderGeometry(0.0201, 0.0194, 0.01, 9), 0.0018, 6), dripMat);
rim3.name = 'rim';
rim3.position.set(0, 0.086, 0);
candle3.add(rim3);
const drip6 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0254, 2, 5), dripMat);
drip6.name = 'drip';
drip6.position.set(0.0162, 0.0703, 0.0092);
candle3.add(drip6);
const drip7 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0216, 2, 5), dripMat);
drip7.name = 'drip';
drip7.position.set(0.0041, 0.0722, 0.0182);
candle3.add(drip7);
const drip8 = new THREE.Mesh(new THREE.CapsuleGeometry(0.0045, 0.0341, 2, 5), dripMat);
drip8.name = 'drip';
drip8.position.set(0.0098, 0.0659, 0.0159);
candle3.add(drip8);
const wick3 = new THREE.Mesh(cylinderGeo, wickMat);
wick3.name = 'wick';
wick3.position.set(0, 0.094, 0);
candle3.add(wick3);
const flameBase3 = new THREE.Mesh(sphereGeo, flameOutMat);
flameBase3.name = 'flameBase';
flameBase3.position.set(0, 0.103, 0);
flameBase3.scale.set(1, 1.15, 1);
candle3.add(flameBase3);
const flameTip3 = new THREE.Mesh(coneGeo, flameInMat);
flameTip3.name = 'flameTip';
flameTip3.position.set(0, 0.12, 0);
candle3.add(flameTip3);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createCandle;
Ratings & comments
0 comments
No comments yet. Share how you used this model or what could be better.
Related models
Same category or similar tags









