592 üçgen0,34 × 0,34 × 0,05 m
Kodda kullan
Editörde remix’lewall-clock.js · 267 satır
Bağımsız bir ES modülü — yalnızca three paketine bağımlıdır. Fabrika fonksiyonunu çağırıp dönen grubu sahnene ekle.
/**
* Wall Clock — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: wall mount point (back of the model on the wall plane z = 0)
* Triangles: 592 · Size (W × H × D): 0.34 × 0.34 × 0.05 m
*
* Usage:
* import { createWallClock } from './wall-clock.js';
*
* const model = createWallClock();
* model.position.set(0, 0, 0);
* scene.add(model);
*
* // Animations: idle
* const mixer = new THREE.AnimationMixer(model);
* mixer.clipAction(THREE.AnimationClip.findByName(model.animations, 'idle')).play();
* // in your render loop: mixer.update(clock.getDelta());
*/
import * as THREE from 'three';
const deg = THREE.MathUtils.degToRad;
// 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);
}
// Extrude a 2D outline along Z (centered). bevel = [thickness, size, segments].
function extrude(outline, depth, holes = [], bevel = null) {
const shape = new THREE.Shape(outline.map(([x, y]) => new THREE.Vector2(x, y)));
for (const hole of holes) shape.holes.push(new THREE.Path(hole.map(([x, y]) => new THREE.Vector2(x, y))));
const options = bevel
? { depth, bevelEnabled: true, bevelThickness: bevel[0], bevelSize: bevel[1], bevelSegments: bevel[2] }
: { depth, bevelEnabled: false };
return new THREE.ExtrudeGeometry(shape, options).translate(0, 0, -depth / 2);
}
// 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 createWallClock() {
const root = new THREE.Group();
root.name = 'WallClock';
// Materials
const frameMat = new THREE.MeshStandardMaterial({ name: 'frame', color: 0x6e4a2c, roughness: 0.6, metalness: 0.1, flatShading: true });
const backMat = new THREE.MeshStandardMaterial({ name: 'back', color: 0x48301d, roughness: 0.8, flatShading: true });
const dialMat = new THREE.MeshStandardMaterial({ name: 'dial', color: 0xf2ead8, roughness: 0.7, flatShading: true });
const inkMat = new THREE.MeshStandardMaterial({ name: 'ink', color: 0x26262b, roughness: 0.6, flatShading: true });
const handMat = new THREE.MeshStandardMaterial({ name: 'hand', color: 0x1f1f24, roughness: 0.4, metalness: 0.4, flatShading: true });
const secondMat = new THREE.MeshStandardMaterial({ name: 'second', color: 0xc23a2c, roughness: 0.5, flatShading: true });
const brassMat = new THREE.MeshStandardMaterial({ name: 'brass', color: 0xc9a04a, roughness: 0.35, metalness: 0.7, flatShading: true });
// Shared geometries
const boxGeo = new THREE.BoxGeometry(0.011, 0.03, 0.003);
const boxGeo2 = new THREE.BoxGeometry(0.006, 0.018, 0.003);
// Parts
const case2 = new THREE.Mesh(new THREE.CylinderGeometry(0.15, 0.15, 0.029, 20), backMat);
case2.name = 'case';
case2.position.set(0, 0, 0.0145);
case2.rotation.set(deg(90), 0, 0);
root.add(case2);
const dial = new THREE.Mesh(new THREE.CircleGeometry(0.147, 20), dialMat);
dial.name = 'dial';
dial.position.set(0, 0, 0.03);
root.add(dial);
const bezel = new THREE.Mesh(lathe([[0.164, 0], [0.17, 0.026], [0.164, 0.044], [0.151, 0.048], [0.143, 0.038], [0.143, 0.026], [0.164, 0]], 20), frameMat);
bezel.name = 'bezel';
bezel.rotation.set(deg(90), 0, 0);
root.add(bezel);
const majorMarker = new THREE.Mesh(boxGeo, inkMat);
majorMarker.name = 'majorMarker';
majorMarker.position.set(0, 0.114, 0.0315);
root.add(majorMarker);
const marker = new THREE.Mesh(boxGeo2, inkMat);
marker.name = 'marker';
marker.position.set(0.06, 0.1039, 0.0315);
marker.rotation.set(0, 0, deg(-30));
root.add(marker);
const marker2 = new THREE.Mesh(boxGeo2, inkMat);
marker2.name = 'marker';
marker2.position.set(0.1039, 0.06, 0.0315);
marker2.rotation.set(0, 0, deg(-60));
root.add(marker2);
const majorMarker2 = new THREE.Mesh(boxGeo, inkMat);
majorMarker2.name = 'majorMarker';
majorMarker2.position.set(0.114, 0, 0.0315);
majorMarker2.rotation.set(0, 0, deg(-90));
root.add(majorMarker2);
const marker3 = new THREE.Mesh(boxGeo2, inkMat);
marker3.name = 'marker';
marker3.position.set(0.1039, -0.06, 0.0315);
marker3.rotation.set(0, 0, deg(-120));
root.add(marker3);
const marker4 = new THREE.Mesh(boxGeo2, inkMat);
marker4.name = 'marker';
marker4.position.set(0.06, -0.1039, 0.0315);
marker4.rotation.set(0, 0, deg(-150));
root.add(marker4);
const majorMarker3 = new THREE.Mesh(boxGeo, inkMat);
majorMarker3.name = 'majorMarker';
majorMarker3.position.set(0, -0.114, 0.0315);
majorMarker3.rotation.set(0, 0, deg(-180));
root.add(majorMarker3);
const marker5 = new THREE.Mesh(boxGeo2, inkMat);
marker5.name = 'marker';
marker5.position.set(-0.06, -0.1039, 0.0315);
marker5.rotation.set(0, 0, deg(-210));
root.add(marker5);
const marker6 = new THREE.Mesh(boxGeo2, inkMat);
marker6.name = 'marker';
marker6.position.set(-0.1039, -0.06, 0.0315);
marker6.rotation.set(0, 0, deg(-240));
root.add(marker6);
const majorMarker4 = new THREE.Mesh(boxGeo, inkMat);
majorMarker4.name = 'majorMarker';
majorMarker4.position.set(-0.114, 0, 0.0315);
majorMarker4.rotation.set(0, 0, deg(-270));
root.add(majorMarker4);
const marker7 = new THREE.Mesh(boxGeo2, inkMat);
marker7.name = 'marker';
marker7.position.set(-0.1039, 0.06, 0.0315);
marker7.rotation.set(0, 0, deg(-300));
root.add(marker7);
const marker8 = new THREE.Mesh(boxGeo2, inkMat);
marker8.name = 'marker';
marker8.position.set(-0.06, 0.1039, 0.0315);
marker8.rotation.set(0, 0, deg(-330));
root.add(marker8);
const hourHand = new THREE.Mesh(extrude([[-0.0054, -0.014], [0.0054, -0.014], [0.009, 0.0465], [0, 0.075], [-0.009, 0.0465]], 0.003), handMat);
hourHand.name = 'hourHand';
hourHand.position.set(0, 0, 0.034);
hourHand.rotation.set(0, 0, deg(-305));
root.add(hourHand);
const minuteHand = new THREE.Mesh(extrude([[-0.0042, -0.018], [0.0042, -0.018], [0.007, 0.0701], [0, 0.113], [-0.007, 0.0701]], 0.003), handMat);
minuteHand.name = 'minuteHand';
minuteHand.position.set(0, 0, 0.038);
minuteHand.rotation.set(0, 0, deg(-60));
root.add(minuteHand);
const secondPivot = new THREE.Group();
secondPivot.name = 'secondPivot';
secondPivot.position.set(0, 0, 0.041);
secondPivot.rotation.set(0, 0, deg(-192));
root.add(secondPivot);
const secondHand = new THREE.Mesh(new THREE.BoxGeometry(0.0025, 0.13, 0.002), secondMat);
secondHand.name = 'secondHand';
secondHand.position.set(0, 0.043, 0);
secondPivot.add(secondHand);
const secondCounterweight = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.008, 0.002, 8), secondMat);
secondCounterweight.name = 'secondCounterweight';
secondCounterweight.position.set(0, -0.022, 0);
secondCounterweight.rotation.set(deg(90), 0, 0);
secondPivot.add(secondCounterweight);
const centerCap = new THREE.Mesh(new THREE.CylinderGeometry(0.009, 0.011, 0.012, 8), brassMat);
centerCap.name = 'centerCap';
centerCap.position.set(0, 0, 0.037);
centerCap.rotation.set(deg(90), 0, 0);
root.add(centerCap);
// Animations ('idle') — play them with THREE.AnimationMixer
root.animations = codelsClips(THREE, [
{ target: minuteHand, anim: [{ type: 'spin', axis: 'z', speed: -0.1 }] },
{ target: secondPivot, anim: [{ type: 'spin', axis: 'z', speed: -6 }] },
]);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createWallClock;
Puanlar ve yorumlar
0 yorum
Henüz yorum yok. Bu modeli nasıl kullandığını ya da neyin daha iyi olabileceğini paylaş.
Benzer modeller
Aynı kategori ya da benzer etiketler









