668 tris3,30 × 2,58 × 2,85 m
Use no código
Fazer remix no editorasteroid.js · 233 linhas
Módulo ES autocontido — depende apenas do three. Chame a factory e adicione o grupo à sua cena.
/**
* Asteroid — Codels 3D model library
* License: CC0-1.0 · Units: meters · Y-up · Front: +Z · Origin: center of the model (it floats — pickups, planets)
* Triangles: 668 · Size (W × H × D): 3.30 × 2.58 × 2.85 m
*
* Usage:
* import { createAsteroid } from './asteroid.js';
*
* const model = createAsteroid();
* 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;
// 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);
}
// 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 createAsteroid() {
const root = new THREE.Group();
root.name = 'Asteroid';
// Materials
const rockMat = new THREE.MeshStandardMaterial({ name: 'rock', color: 0x625952, roughness: 0.95, flatShading: true });
const darkMat = new THREE.MeshStandardMaterial({ name: 'dark', color: 0x3e3732, roughness: 0.95, flatShading: true });
// Parts
const asteroid = new THREE.Group();
asteroid.name = 'asteroid';
asteroid.scale.setScalar(3);
root.add(asteroid);
const body = new THREE.Mesh(jitter(new THREE.IcosahedronGeometry(0.5, 2), 0.03, 7), rockMat);
body.name = 'body';
body.scale.set(1.0892, 0.8051, 0.951);
asteroid.add(body);
const bulge1a = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.2209, 0), 0.0331, 7), darkMat);
bulge1a.name = 'bulge1a';
bulge1a.position.set(-0.2263, -0.2165, -0.113);
bulge1a.rotation.set(deg(78.938), deg(48.9414), 0);
asteroid.add(bulge1a);
const bulge1b = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1705, 0), 0.0256, 8), rockMat);
bulge1b.name = 'bulge1b';
bulge1b.position.set(0.2263, 0.2165, 0.113);
bulge1b.rotation.set(deg(5.0502), deg(58.5076), 0);
asteroid.add(bulge1b);
const bulge2a = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.2071, 0), 0.0311, 10), rockMat);
bulge2a.name = 'bulge2a';
bulge2a.position.set(0.3214, -0.1459, 0.0938);
bulge2a.rotation.set(deg(38.22), deg(55.6337), 0);
asteroid.add(bulge2a);
const bulge2b = new THREE.Mesh(jitter(new THREE.DodecahedronGeometry(0.1907, 0), 0.0286, 11), rockMat);
bulge2b.name = 'bulge2b';
bulge2b.position.set(-0.3214, 0.1459, -0.0938);
bulge2b.rotation.set(deg(18.7105), deg(81.5237), 0);
asteroid.add(bulge2b);
const crater1 = new THREE.Group();
crater1.name = 'crater1';
crater1.position.set(0.2815, 0.1779, 0.3486);
crater1.rotation.set(deg(54.5429), 0, deg(-26.6296));
asteroid.add(crater1);
const cylinder8 = new THREE.Mesh(new THREE.CylinderGeometry(0.1237, 0.0781, 0.06, 8), darkMat);
cylinder8.position.set(0, -0.018, 0);
crater1.add(cylinder8);
const lathe9 = new THREE.Mesh(lathe([[0.1954, -0.04], [0.1407, 0.026], [0.1224, 0.02], [0.1146, -0.03]], 9), rockMat);
crater1.add(lathe9);
const crater2 = new THREE.Group();
crater2.name = 'crater2';
crater2.position.set(-0.1652, 0.3446, 0.1989);
crater2.rotation.set(deg(22.4759), 0, deg(13.6077));
asteroid.add(crater2);
const cylinder11 = new THREE.Mesh(new THREE.CylinderGeometry(0.1226, 0.0774, 0.06, 8), darkMat);
cylinder11.position.set(0, -0.018, 0);
crater2.add(cylinder11);
const lathe12 = new THREE.Mesh(lathe([[0.1936, -0.04], [0.1394, 0.026], [0.1213, 0.02], [0.1136, -0.03]], 9), rockMat);
crater2.add(lathe12);
const crater3 = new THREE.Group();
crater3.name = 'crater3';
crater3.position.set(0.2221, 0.1614, -0.3901);
crater3.rotation.set(deg(-60.0049), 0, deg(-20.6021));
asteroid.add(crater3);
const cylinder14 = new THREE.Mesh(new THREE.CylinderGeometry(0.1084, 0.0685, 0.06, 8), darkMat);
cylinder14.position.set(0, -0.018, 0);
crater3.add(cylinder14);
const lathe15 = new THREE.Mesh(lathe([[0.1712, -0.04], [0.1233, 0.026], [0.1073, 0.02], [0.1005, -0.03]], 9), rockMat);
crater3.add(lathe15);
const crater4 = new THREE.Group();
crater4.name = 'crater4';
crater4.position.set(0.079, 0.3368, -0.2512);
crater4.rotation.set(deg(-28.1328), 0, deg(-6.4463));
asteroid.add(crater4);
const cylinder17 = new THREE.Mesh(new THREE.CylinderGeometry(0.0802, 0.0507, 0.06, 8), darkMat);
cylinder17.position.set(0, -0.018, 0);
crater4.add(cylinder17);
const lathe18 = new THREE.Mesh(lathe([[0.1266, -0.04], [0.0912, 0.026], [0.0794, 0.02], [0.0743, -0.03]], 9), rockMat);
crater4.add(lathe18);
// Animations ('idle') — play them with THREE.AnimationMixer
root.animations = codelsClips(THREE, [
{ target: asteroid, anim: [{ type: 'spin', axis: 'y', speed: 12 }, { type: 'swing', axis: 'x', angle: 25, period: 10 }] },
]);
root.traverse((o) => {
if (o.isMesh) {
o.castShadow = true;
o.receiveShadow = true;
}
});
return root;
}
export default createAsteroid;
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









