Плагин для Blender: параметрические поверхности, экспорт OBJ/TXT, триангуляция сетки
Web-сервис в браузере: ноль установок, мультипользователь, REST API, социальные функции
Проектирование и разработка многопользовательского web-сервиса для интерактивной 3D-визуализации параметрических поверхностей и данных CSV с экспортом моделей
Объект: клиент-серверные web-приложения для 3D-графики
Предмет: алгоритмы визуализации и обработки 3D-поверхностей в браузере
Задачи
<script src="...">, деплой — копирование файлов
// Дискретизация + триангуляция
for (let i = 0; i <= uSeg; i++) {
for (let j = 0; j <= vSeg; j++) {
const u = uMin + i * uStep;
const v = vMin + j * vStep;
vertices.push(
parser.evaluate(eqX, {u,v}) * scale,
parser.evaluate(eqY, {u,v}) * scale,
parser.evaluate(eqZ, {u,v}) * scale
);
}
}
// Quad → два треугольника
for (let i = 0; i < uSeg; i++) {
for (let j = 0; j < vSeg; j++) {
const a = i*(vSeg+1)+j, b=a+1,
c = (i+1)*(vSeg+1)+j, d=c+1;
indices.push(a,b,d, a,d,c);
}
}
geometry.computeVertexNormals();
evaluate(expression, variables = {}) {
let expr = expression.trim();
// Препроцессинг: ** → ^, Math. → ''
expr = this.preprocessExpression(expr);
expr = this.replaceConstants(expr); // pi→3.14…
expr = this.validateExpression(expr); // whitelist
return this.evaluateExpression(expr, variables);
}
tokenize(expr) {
// числа, функции из whitelist, переменные u/v,
// операторы +−*/^, скобки ()
// → массив токенов [{type, value}, ...]
}
shuntingYard(tokens) {
// алгоритм Дейкстры: стек операторов
// + очередь вывода → постфиксная запись
// приоритеты: ^ > *,/ > +,−
}
evaluateRPN(rpn, variables) {
// стековый калькулятор → число
}
multipart/csvFile → Multer → disk
; \t , |
nx × ny, пропуски → z=0
// Сервер: buildGrid(points)
const xs = [...new Set(pts.map(p=>p[0]))].sort((a,b)=>a-b);
const ys = [...new Set(pts.map(p=>p[1]))].sort((a,b)=>a-b);
const grid = new Array(xs.length * ys.length).fill(null);
for (const [x, y, z] of pts) {
const i = xs.indexOf(x), j = ys.indexOf(y);
grid[i * ys.length + j] = [x, y, z];
}
// Пропуски → z = 0
const filled = grid.map((p, idx) => p ?? [
xs[Math.floor(idx/ys.length)],
ys[idx % ys.length], 0
]);
// Клиент: маппинг координат
const [cx, cy, cz] = grid[i * ny + j];
vertices.push(cx, cz, cy); // CSV(x,y,z)→THREEjs(x,z,y)
t ∈ [0, 1] определяет цвет вершины в выбранной палитре. WebGL интерполирует цвет по треугольникам.// Кусочно-линейная интерполяция палитры
_lerp(t, stops) {
for (let i = 0; i < stops.length - 1; i++) {
const [t0, c0] = stops[i];
const [t1, c1] = stops[i + 1];
if (t >= t0 && t <= t1) {
const f = (t - t0) / (t1 - t0);
return [
c0[0] + (c1[0]-c0[0]) * f,
c0[1] + (c1[1]-c0[1]) * f,
c0[2] + (c1[2]-c0[2]) * f
];
}
}
}
_viridis(t) {
return this._lerp(t, [
[0.00, [0.267, 0.005, 0.329]],
[0.25, [0.230, 0.322, 0.546]],
[0.50, [0.128, 0.566, 0.551]],
[0.75, [0.369, 0.788, 0.384]],
[1.00, [0.993, 0.906, 0.144]]
]);
}
C = центр масс, R = max расстояние → топологическая инверсия модели
exportSurfaceAsOBJ(surface) {
const {vertices: v, triangleIndices: idx} = surface;
let obj = `# Surface: ${surface.name}\n`;
obj += `# ${new Date().toISOString()}\n\n`;
for (let i = 0; i < v.length; i += 3)
obj += `v ${v[i].toFixed(6)} `
+ `${v[i+1].toFixed(6)} `
+ `${v[i+2].toFixed(6)}\n`;
const normals = this.calculateNormals(v, idx);
for (let i = 0; i < normals.length; i += 3)
obj += `vn ${normals[i].toFixed(6)} `
+ `${normals[i+1].toFixed(6)} `
+ `${normals[i+2].toFixed(6)}\n`;
obj += `\ng ${surface.name}\n`;
for (let i = 0; i < idx.length; i += 3) {
const a=idx[i]+1, b=idx[i+1]+1, c=idx[i+2]+1;
obj += `f ${a}//${a} ${b}//${b} ${c}//${c}\n`;
}
return obj;
}
localStorage + автовосстановление сессииbcrypt.compare()// Регистрация
const hash = await bcrypt.hash(password, 10);
db.run(`INSERT INTO users
(username,email,password_hash)
VALUES (?,?,?)`, [name,email,hash]);
// Вход
const user = db.get(`SELECT * FROM users
WHERE email = ?`, [email]);
const ok = await bcrypt.compare(password, user.hash);
if (ok) {
const token = jwt.sign(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '7d' }
);
res.json({ token });
}
Демо-сервис
https://3d.gr8brite.ru
Спасибо
Готов ответить на вопросы