632 tris0,92 × 1,08 × 0,56 m
Use no código
Fazer remix no editorlever.js · 233 linhas
Módulo ES autocontido — depende apenas do three. Chame a factory e adicione o grupo à sua cena.
/**
* Lever — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the base (y = 0)
* Triangles: 632 · Size (W × H × D): 0.92 × 1.07 × 0.56 m
*
* Usage:
* import { createLever } from './lever.js';
*
* const model = createLever();
* model.position.set(0, 0, 0);
* scene.add(model);
*
* // Animations: pull
* const mixer = new THREE.AnimationMixer(model);
* const pull = mixer.clipAction(THREE.AnimationClip.findByName(model.animations, 'pull'));
* pull.setLoop(THREE.LoopOnce, 1);
* pull.clampWhenFinished = true;
* pull.play(); // back: pull.timeScale = -1; pull.paused = false;
* // in your render loop: mixer.update(clock.getDelta());
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// Build animation clips from simple descriptions (Codels). Play them with THREE.AnimationMixer.
function codelsClips(THREE, entries) {
var TAU = Math.PI * 2;
var DEG = Math.PI / 180;
var AX = { x: new THREE.Vector3(1, 0, 0), y: new THREE.Vector3(0, 1, 0), z: new THREE.Vector3(0, 0, 1) };
var periodOf = function (a) { return a.type === 'spin' ? 360 / Math.abs(a.speed) : a.period; };
var clips = [];
// loops → 'idle'
var loops = [];
entries.forEach(function (e) {
var l = e.anim.filter(function (a) { return a.type !== 'hinge'; });
if (l.length) loops.push({ target: e.target, anim: l });
});
if (loops.length) {
var periods = [];
loops.forEach(function (e) { e.anim.forEach(function (a) { periods.push(periodOf(a)); }); });
var longest = Math.max.apply(null, periods);
var D = longest;
var bestErr = Infinity;
for (var m = 1; m <= 6 && bestErr > 0.01; m++) {
var err = 0;
periods.forEach(function (p) {
var n = Math.max(1, Math.round((longest * m) / p));
err = Math.max(err, Math.abs((longest * m) / n - p) / p);
});
if (err < bestErr - 1e-9) { bestErr = err; D = longest * m; }
}
var cycles = function (a) { return Math.max(1, Math.round(D / periodOf(a))); };
var steps = 24;
loops.forEach(function (e) { e.anim.forEach(function (a) { steps = Math.max(steps, cycles(a) * 24); }); });
steps = Math.min(steps, 720);
var tracks = [];
loops.forEach(function (e) {
var o = e.target;
var q0 = o.quaternion.clone(), p0 = o.position.clone(), s0 = o.scale.clone();
var q = new THREE.Quaternion(), turn = new THREE.Quaternion(), p = new THREE.Vector3(), s = new THREE.Vector3();
var has = function (type) { return e.anim.some(function (a) { return a.type === type; }); };
var turns = has('spin') || has('swing'), moves = has('bob'), grows = has('pulse');
var times = [], qv = [], pv = [], sv = [];
for (var k = 0; k <= steps; k++) {
var t = (D * k) / steps;
q.copy(q0); p.copy(p0); s.copy(s0);
e.anim.forEach(function (a) {
var n = cycles(a);
var w = Math.sin(TAU * ((n * t) / D + (a.phase || 0)));
if (a.type === 'spin') q.multiply(turn.setFromAxisAngle(AX[a.axis], (a.speed < 0 ? -TAU : TAU) * ((n * t) / D)));
else if (a.type === 'swing') q.multiply(turn.setFromAxisAngle(AX[a.axis], a.angle * DEG * w));
else if (a.type === 'bob') p.addScaledVector(AX[a.axis], a.distance * w);
else if (a.type === 'pulse') s.multiplyScalar(1 + a.amount * w);
});
times.push(t);
if (turns) qv.push(q.x, q.y, q.z, q.w);
if (moves) pv.push(p.x, p.y, p.z);
if (grows) sv.push(s.x, s.y, s.z);
}
if (turns) tracks.push(new THREE.QuaternionKeyframeTrack(o.uuid + '.quaternion', times, qv));
if (moves) tracks.push(new THREE.VectorKeyframeTrack(o.uuid + '.position', times, pv));
if (grows) tracks.push(new THREE.VectorKeyframeTrack(o.uuid + '.scale', times, sv));
});
clips.push(new THREE.AnimationClip('idle', D, tracks));
}
// hinge moves → one-shot clips ('open' …)
var shots = {};
entries.forEach(function (e) {
e.anim.forEach(function (a) {
if (a.type !== 'hinge') return;
var name = a.clip || 'open';
(shots[name] = shots[name] || []).push({ target: e.target, a: a });
});
});
Object.keys(shots).forEach(function (name) {
var tracks = [];
var length = 0;
shots[name].forEach(function (h) {
var o = h.target, q0 = o.quaternion.clone(), q = new THREE.Quaternion(), turn = new THREE.Quaternion();
var n = Math.max(4, Math.ceil(Math.abs(h.a.angle) / 10));
var times = [], qv = [];
for (var k = 0; k <= n; k++) {
var f = k / n;
var eased = f * f * (3 - 2 * f);
q.copy(q0).multiply(turn.setFromAxisAngle(AX[h.a.axis], h.a.angle * DEG * eased));
times.push(h.a.duration * f);
qv.push(q.x, q.y, q.z, q.w);
}
length = Math.max(length, h.a.duration);
tracks.push(new THREE.QuaternionKeyframeTrack(o.uuid + '.quaternion', times, qv));
});
clips.push(new THREE.AnimationClip(name, length, tracks));
});
return clips;
}
export function createLever() {
const root = new THREE.Group();
root.name = 'Lever';
// Materials
const housingMat = new THREE.MeshStandardMaterial({ name: 'housing', color: 0x4a5262, roughness: 0.55, metalness: 0.4, flatShading: true });
const darkMat = new THREE.MeshStandardMaterial({ name: 'dark', color: 0x262a33, roughness: 0.7, metalness: 0.3, flatShading: true });
const steelMat = new THREE.MeshStandardMaterial({ name: 'steel', color: 0xc9cfd8, roughness: 0.25, metalness: 0.8, flatShading: true });
const knobMat = new THREE.MeshStandardMaterial({ name: 'knob', color: 0xe23b2e, roughness: 0.25, flatShading: true });
const rivetMat = new THREE.MeshStandardMaterial({ name: 'rivet', color: 0x8e97a6, roughness: 0.4, metalness: 0.6, flatShading: true });
const redLampMat = new THREE.MeshStandardMaterial({ name: 'redLamp', color: 0x6e2620, roughness: 0.4, flatShading: true });
const greenLampMat = new THREE.MeshStandardMaterial({ name: 'greenLamp', color: 0x6dff7a, roughness: 0.3, emissive: 0x2cff4a, emissiveIntensity: 1.1, flatShading: true });
// Shared geometries
const cylinderGeo = new THREE.CylinderGeometry(0.022, 0.022, 0.02, 6);
const cylinderGeo2 = new THREE.CylinderGeometry(0.055, 0.06, 0.03, 10);
const sphereGeo = new THREE.SphereGeometry(0.042, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2);
// Parts
const foot = new THREE.Mesh(new THREE.BoxGeometry(0.86, 0.06, 0.52), darkMat);
foot.name = 'foot';
foot.position.set(0, 0.03, 0);
root.add(foot);
const housing = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.22, 0.46), housingMat);
housing.name = 'housing';
housing.position.set(0, 0.17, 0);
root.add(housing);
const topPlate = new THREE.Mesh(new THREE.BoxGeometry(0.74, 0.03, 0.4), darkMat);
topPlate.name = 'topPlate';
topPlate.position.set(0, 0.29, 0);
root.add(topPlate);
const slotRim = new THREE.Mesh(new THREE.BoxGeometry(0.62, 0.012, 0.09), housingMat);
slotRim.name = 'slotRim';
slotRim.position.set(0, 0.306, 0);
root.add(slotRim);
const slot = new THREE.Mesh(new THREE.BoxGeometry(0.56, 0.014, 0.05), darkMat);
slot.name = 'slot';
slot.position.set(0, 0.309, 0);
root.add(slot);
const drum = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, 0.2, 12), housingMat);
drum.name = 'drum';
drum.position.set(0, 0.3, 0);
drum.rotation.set(deg(90), 0, 0);
root.add(drum);
const pivotPin = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.24, 8), steelMat);
pivotPin.name = 'pivotPin';
pivotPin.position.set(0, 0.3, 0);
pivotPin.rotation.set(deg(90), 0, 0);
root.add(pivotPin);
const cylinder8 = new THREE.Mesh(cylinderGeo, rivetMat);
cylinder8.position.set(-0.34, 0.31, -0.17);
root.add(cylinder8);
const cylinder9 = new THREE.Mesh(cylinderGeo, rivetMat);
cylinder9.position.set(0.34, 0.31, -0.17);
root.add(cylinder9);
const cylinder10 = new THREE.Mesh(cylinderGeo, rivetMat);
cylinder10.position.set(0.34, 0.31, 0.17);
root.add(cylinder10);
const cylinder11 = new THREE.Mesh(cylinderGeo, rivetMat);
cylinder11.position.set(-0.34, 0.31, 0.17);
root.add(cylinder11);
const handle = new THREE.Group();
handle.name = 'handle';
handle.position.set(0, 0.3, 0);
handle.rotation.set(0, 0, deg(-30));
root.add(handle);
const shaft = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.04, 0.72, 8), steelMat);
shaft.name = 'shaft';
shaft.position.set(0, 0.36, 0);
handle.add(shaft);
const grip = new THREE.Mesh(new THREE.CylinderGeometry(0.055, 0.05, 0.16, 8), darkMat);
grip.name = 'grip';
grip.position.set(0, 0.6, 0);
handle.add(grip);
const knob = new THREE.Mesh(new THREE.SphereGeometry(0.1, 10, 8), knobMat);
knob.name = 'knob';
knob.position.set(0, 0.78, 0);
handle.add(knob);
const lampRimOff = new THREE.Mesh(cylinderGeo2, darkMat);
lampRimOff.name = 'lampRimOff';
lampRimOff.position.set(-0.22, 0.154, 0.242);
lampRimOff.rotation.set(deg(90), 0, 0);
root.add(lampRimOff);
const lampOff = new THREE.Mesh(sphereGeo, redLampMat);
lampOff.name = 'lampOff';
lampOff.position.set(-0.22, 0.154, 0.255);
lampOff.rotation.set(deg(90), 0, 0);
root.add(lampOff);
const lampRimOn = new THREE.Mesh(cylinderGeo2, darkMat);
lampRimOn.name = 'lampRimOn';
lampRimOn.position.set(0.22, 0.154, 0.242);
lampRimOn.rotation.set(deg(90), 0, 0);
root.add(lampRimOn);
const lampOn = new THREE.Mesh(sphereGeo, greenLampMat);
lampOn.name = 'lampOn';
lampOn.position.set(0.22, 0.154, 0.255);
lampOn.rotation.set(deg(90), 0, 0);
root.add(lampOn);
// Animations ('pull') — play them with THREE.AnimationMixer
root.animations = codelsClips(THREE, [
{ target: handle, anim: [{ type: 'hinge', axis: 'z', angle: 60, duration: 0.5, clip: 'pull' }] },
]);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createLever;
Avaliações e comentários
0 comentário
Nenhum comentário ainda. Conte como você usou este modelo ou o que poderia melhorar.
Modelos relacionados
Mesma categoria ou tags parecidas









