mirror of
https://github.com/robonen/canvas-3d.git
synced 2026-03-20 02:44:40 +00:00
refactor(app): update deps, decomposition, formatting
This commit is contained in:
@@ -2,11 +2,3 @@ APP_NAME=canvas-3d
|
|||||||
|
|
||||||
# Infrastucture config
|
# Infrastucture config
|
||||||
FORWARD_APP_PORT=3000
|
FORWARD_APP_PORT=3000
|
||||||
|
|
||||||
FORWARD_DB_PORT=5432
|
|
||||||
DB_USERNAME=postgres
|
|
||||||
DB_PASSWORD=password
|
|
||||||
DB_DATABASE=c3d
|
|
||||||
|
|
||||||
# App config
|
|
||||||
NUXT_API_HOST=http://localhost/api
|
|
||||||
|
|||||||
3
.husky/pre-commit
Normal file → Executable file
3
.husky/pre-commit
Normal file → Executable file
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
npm run typecheck
|
npm run type:check
|
||||||
|
npm run style:check
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ARG NODE_VERSION=18.12-slim
|
ARG NODE_VERSION=22.2.0-slim
|
||||||
|
|
||||||
# Stage 1: Build the application
|
# Stage 1: Build the application
|
||||||
FROM node:${NODE_VERSION} AS builder
|
FROM node:${NODE_VERSION} AS builder
|
||||||
|
|||||||
13
Taskfile.yml
13
Taskfile.yml
@@ -1,6 +1,6 @@
|
|||||||
version: '3'
|
version: '3'
|
||||||
|
|
||||||
dotenv: [ '.env' ]
|
dotenv: ['.env']
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
make:env:
|
make:env:
|
||||||
@@ -13,17 +13,17 @@ tasks:
|
|||||||
|
|
||||||
local:run:
|
local:run:
|
||||||
ignore_error: true
|
ignore_error: true
|
||||||
deps: [ make:env, make:install ]
|
deps: [make:env, make:install]
|
||||||
cmds:
|
cmds:
|
||||||
- npm run dev
|
- npm run dev
|
||||||
|
|
||||||
docker:run:
|
docker:run:
|
||||||
deps: [ make:env ]
|
deps: [make:env]
|
||||||
cmds:
|
cmds:
|
||||||
- docker-compose -p $APP_NAME up -d
|
- docker-compose -p $APP_NAME up -d
|
||||||
|
|
||||||
docker:run:build:
|
docker:run:build:
|
||||||
deps: [ make:env ]
|
deps: [make:env]
|
||||||
cmds:
|
cmds:
|
||||||
- docker-compose -p $APP_NAME up -d --build
|
- docker-compose -p $APP_NAME up -d --build
|
||||||
|
|
||||||
@@ -34,8 +34,3 @@ tasks:
|
|||||||
docker:stop:rm:
|
docker:stop:rm:
|
||||||
cmds:
|
cmds:
|
||||||
- docker-compose -p $APP_NAME down -v --remove-orphans
|
- docker-compose -p $APP_NAME down -v --remove-orphans
|
||||||
|
|
||||||
test:performance:
|
|
||||||
ignore_error: true
|
|
||||||
cmds:
|
|
||||||
- docker run --rm -i --network host grafana/k6 run --vus 1000 --duration 30s - < ./src/tests/performance/test_main_page.js
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
version: '3'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
proxy_lb:
|
proxy_lb:
|
||||||
container_name: proxy_lb
|
container_name: proxy_lb
|
||||||
image: nginx:latest
|
image: nginx:latest
|
||||||
@@ -26,27 +23,6 @@ services:
|
|||||||
- '${FORWARD_APP_PORT:-3000}'
|
- '${FORWARD_APP_PORT:-3000}'
|
||||||
networks:
|
networks:
|
||||||
- c3d_net
|
- c3d_net
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
|
|
||||||
db:
|
|
||||||
container_name: db
|
|
||||||
image: 'postgres:latest'
|
|
||||||
ports:
|
|
||||||
- '${FORWARD_DB_PORT:-5432}:5432'
|
|
||||||
environment:
|
|
||||||
PGPASSWORD: '${DB_PASSWORD:-secret}'
|
|
||||||
POSTGRES_DB: '${DB_DATABASE}'
|
|
||||||
POSTGRES_USER: '${DB_USERNAME}'
|
|
||||||
POSTGRES_PASSWORD: '${DB_PASSWORD:-secret}'
|
|
||||||
volumes:
|
|
||||||
- 'c3d_vol:/var/lib/postgresql/data'
|
|
||||||
networks:
|
|
||||||
- c3d_net
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD", "pg_isready", "-q", "-d", "${DB_DATABASE}", "-U", "${DB_USERNAME}" ]
|
|
||||||
retries: 3
|
|
||||||
timeout: 5s
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
c3d_vol:
|
c3d_vol:
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
// https://v3.nuxtjs.org/api/configuration/nuxt.config
|
|
||||||
import { resolve } from 'path';
|
|
||||||
|
|
||||||
const SRC = resolve(__dirname, 'src');
|
|
||||||
const PACKAGES = resolve(__dirname, 'packages');
|
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
srcDir: SRC,
|
srcDir: 'src',
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
imports: {
|
||||||
|
dirs: ['const'],
|
||||||
|
},
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
title: 'Canvas 3D',
|
title: 'Canvas 3D',
|
||||||
@@ -14,9 +11,5 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
css: ['@/assets/styles/main.scss'],
|
css: ['@/assets/styles/main.scss'],
|
||||||
typescript: {
|
devtools: { enabled: true },
|
||||||
typeCheck: true,
|
|
||||||
shim: false,
|
|
||||||
},
|
|
||||||
modules: ['@vueuse/nuxt'],
|
|
||||||
});
|
});
|
||||||
|
|||||||
13760
package-lock.json
generated
13760
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
@@ -8,23 +8,25 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
|
||||||
"dev": "nuxt dev",
|
"dev": "nuxt dev",
|
||||||
"typecheck": "nuxt typecheck",
|
"build": "nuxt build",
|
||||||
"info": "nuxt info",
|
|
||||||
"analyze": "nuxt analyze",
|
|
||||||
"generate": "nuxt generate",
|
|
||||||
"preview": "nuxt preview",
|
"preview": "nuxt preview",
|
||||||
"postinstall": "nuxt prepare",
|
"postinstall": "nuxt prepare",
|
||||||
"prepare": "husky install"
|
"prepare": "husky install",
|
||||||
|
"type:check": "nuxt typecheck",
|
||||||
|
"style:check": "prettier --check .",
|
||||||
|
"style:fix": "prettier --write ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"nuxt": "^3.11.2",
|
||||||
|
"sass": "^1.77.2",
|
||||||
|
"vue": "^3.4.27",
|
||||||
|
"vue-router": "^4.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vueuse/core": "^9.3.1",
|
"husky": "^9.0.11",
|
||||||
"@vueuse/nuxt": "^9.3.1",
|
"prettier": "^3.2.5",
|
||||||
"husky": "^8.0.2",
|
"typescript": "^5.4.5",
|
||||||
"nuxt": "^3.0.0",
|
"vue-tsc": "^2.0.19"
|
||||||
"prettier": "^2.8.0",
|
|
||||||
"sass": "^1.55.0",
|
|
||||||
"vue-tsc": "^1.0.9"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM ubuntu:22.04
|
FROM ubuntu:24.04
|
||||||
|
|
||||||
MAINTAINER Robonen Andrew <robonenandrew@gmail.com>
|
MAINTAINER Robonen Andrew <robonenandrew@gmail.com>
|
||||||
|
|
||||||
|
|||||||
2
packages/matrix/build.sh
Normal file → Executable file
2
packages/matrix/build.sh
Normal file → Executable file
@@ -1,3 +1,3 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/bash
|
||||||
|
|
||||||
docker build -t llvm .
|
docker build -t llvm .
|
||||||
|
|||||||
4
packages/matrix/run.sh
Normal file → Executable file
4
packages/matrix/run.sh
Normal file → Executable file
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/bin/bash
|
||||||
|
|
||||||
docker run --rm \
|
docker run --rm \
|
||||||
-v $(pwd)/src:/src \
|
-v $(pwd)/src:/src \
|
||||||
-v $(pwd)/dist:/dist \
|
-v $(pwd)/dist:/dist \
|
||||||
llvm \
|
llvm \
|
||||||
clang --target=wasm32 -O3 -fno-builtin -flto -nostdlib -Wl,--no-entry -Wl,--export-all -Wl,--lto-O3 -o /dist/matrix.wasm /src/matrix.c
|
clang --target=wasm32 -O3 -fno-builtin -flto -nostdlib -Wl,--no-entry -Wl,--export-all /src/matrix.c
|
||||||
|
|||||||
@@ -5,18 +5,17 @@
|
|||||||
void matrix_mul(double *A, double *B, double *C, unsigned m, unsigned n, unsigned p)
|
void matrix_mul(double *A, double *B, double *C, unsigned m, unsigned n, unsigned p)
|
||||||
{
|
{
|
||||||
unsigned i, j, k;
|
unsigned i, j, k;
|
||||||
double sum;
|
|
||||||
|
|
||||||
for (i = 0; i < m; i++)
|
for (i = 0; i < m; i++)
|
||||||
{
|
{
|
||||||
for (j = 0; j < p; j++)
|
for (j = 0; j < p; j++)
|
||||||
{
|
{
|
||||||
sum = 0;
|
C[i * p + j] = 0;
|
||||||
|
|
||||||
for (k = 0; k < n; k++)
|
for (k = 0; k < n; k++)
|
||||||
{
|
{
|
||||||
sum += A[i * n + k] * B[k * p + j];
|
C[i * p + j] += A[i * n + k] * B[k * p + j];
|
||||||
}
|
}
|
||||||
C[i * p + j] = sum;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10309
pnpm-lock.yaml
generated
Normal file
10309
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
src: url('#{$font-with-path}.eot');
|
src: url('#{$font-with-path}.eot');
|
||||||
src: local('#{$font-with-dash}'), local('#{$font-with-space}'),
|
src:
|
||||||
|
local('#{$font-with-dash}'),
|
||||||
|
local('#{$font-with-space}'),
|
||||||
url('#{$font-with-path}.eot?#iefix') format('embedded-opentype'),
|
url('#{$font-with-path}.eot?#iefix') format('embedded-opentype'),
|
||||||
url('#{$font-with-path}.woff2') format('woff2'),
|
url('#{$font-with-path}.woff2') format('woff2'),
|
||||||
url('#{$font-with-path}.woff') format('woff'),
|
url('#{$font-with-path}.woff') format('woff'),
|
||||||
@@ -19,7 +21,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
src: url('#{$font-with-path}Italic.eot');
|
src: url('#{$font-with-path}Italic.eot');
|
||||||
src: local('#{$font-with-dash}Italic'),
|
src:
|
||||||
|
local('#{$font-with-dash}Italic'),
|
||||||
local('#{$font-with-space} Italic'),
|
local('#{$font-with-space} Italic'),
|
||||||
url('#{$font-with-path}Italic.eot?#iefix') format('embedded-opentype'),
|
url('#{$font-with-path}Italic.eot?#iefix') format('embedded-opentype'),
|
||||||
url('#{$font-with-path}Italic.woff2') format('woff2'),
|
url('#{$font-with-path}Italic.woff2') format('woff2'),
|
||||||
@@ -39,15 +42,16 @@ $fonts: (
|
|||||||
Black: 700,
|
Black: 700,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@include MakeFont('Formular', $fonts, '@/assets/fonts/formular');
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Computer Modern Serif';
|
font-family: 'Computer Modern Serif';
|
||||||
src: url('@/assets/fonts/computer-modern/cmunrm.eot');
|
src: url('@/assets/fonts/computer-modern/cmunrm.eot');
|
||||||
src: url('@/assets/fonts/computer-modern/cmunrm.eot?#iefix')
|
src:
|
||||||
|
url('@/assets/fonts/computer-modern/cmunrm.eot?#iefix')
|
||||||
format('embedded-opentype'),
|
format('embedded-opentype'),
|
||||||
url('@/assets/fonts/computer-modern/cmunrm.woff') format('woff'),
|
url('@/assets/fonts/computer-modern/cmunrm.woff') format('woff'),
|
||||||
url('@/assets/fonts/computer-modern/cmunrm.ttf') format('truetype');
|
url('@/assets/fonts/computer-modern/cmunrm.ttf') format('truetype');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include MakeFont('Formular', $fonts, '@/assets/fonts/formular');
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { title } = defineProps<{ title: string }>();
|
defineProps<{ title: string }>();
|
||||||
|
|
||||||
const showForm = ref<boolean>(false);
|
const showForm = ref<boolean>(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,321 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const canvas = ref<HTMLCanvasElement | null>(null);
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
||||||
|
|
||||||
onMounted(() => {
|
useCanvas(canvas);
|
||||||
if (!canvas.value) return;
|
|
||||||
|
|
||||||
canvas.value.width = window.innerWidth;
|
|
||||||
canvas.value.height = window.innerHeight;
|
|
||||||
|
|
||||||
const ctx = canvas.value.getContext('2d');
|
|
||||||
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
ctx.fillStyle = 'red';
|
|
||||||
|
|
||||||
type Point = [number, number, number, number];
|
|
||||||
|
|
||||||
const sizeX = canvas.value.width;
|
|
||||||
const sizeY = canvas.value.height;
|
|
||||||
|
|
||||||
const centerX = sizeX / 2;
|
|
||||||
const centerY = sizeY / 2;
|
|
||||||
|
|
||||||
const figureSize = 200;
|
|
||||||
|
|
||||||
const figureList = {
|
|
||||||
[Figures.CUBE]: {
|
|
||||||
points: [
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
[0, 0, 200, 1],
|
|
||||||
[0, 200, 0, 1],
|
|
||||||
[0, 200, 200, 1],
|
|
||||||
[200, 0, 0, 1],
|
|
||||||
[200, 0, 200, 1],
|
|
||||||
[200, 200, 0, 1],
|
|
||||||
[200, 200, 200, 1],
|
|
||||||
] as Point[],
|
|
||||||
faces: [
|
|
||||||
[0, 1, 3, 2],
|
|
||||||
[0, 1, 5, 4],
|
|
||||||
[0, 2, 6, 4],
|
|
||||||
[1, 3, 7, 5],
|
|
||||||
[2, 3, 7, 6],
|
|
||||||
[4, 5, 7, 6],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
[Figures.OCTAHEDRON]: {
|
|
||||||
points: [
|
|
||||||
[0, 0, 100, 1],
|
|
||||||
[100, 100, 0, 1],
|
|
||||||
[100, -100, 0, 1],
|
|
||||||
[-100, -100, 0, 1],
|
|
||||||
[-100, 100, 0, 1],
|
|
||||||
[0, 0, -100, 1],
|
|
||||||
] as Point[],
|
|
||||||
faces: [
|
|
||||||
[0, 1, 2],
|
|
||||||
[0, 2, 3],
|
|
||||||
[0, 3, 4],
|
|
||||||
[0, 4, 1],
|
|
||||||
[5, 1, 2],
|
|
||||||
[5, 2, 3],
|
|
||||||
[5, 3, 4],
|
|
||||||
[5, 4, 1],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
[Figures.TRIHEDRAL_PYRAMID]: {
|
|
||||||
points: [
|
|
||||||
[0, 0, 100, 1],
|
|
||||||
[0, 80, 0, 1],
|
|
||||||
[86.6, -50, 0, 1],
|
|
||||||
[-86.6, -50, 0, 1],
|
|
||||||
] as Point[],
|
|
||||||
faces: [
|
|
||||||
[0, 1, 2],
|
|
||||||
[0, 2, 3],
|
|
||||||
[0, 3, 1],
|
|
||||||
[1, 2, 3],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
[Figures.SQUARE_PYRAMID]: {
|
|
||||||
points: [
|
|
||||||
[0, 0, figureSize, 1],
|
|
||||||
[100, 100, 0, 1],
|
|
||||||
[100, -100, 0, 1],
|
|
||||||
[-100, -100, 0, 1],
|
|
||||||
[-100, 100, 0, 1],
|
|
||||||
] as Point[],
|
|
||||||
faces: [
|
|
||||||
[0, 1, 2],
|
|
||||||
[0, 2, 3],
|
|
||||||
[0, 3, 4],
|
|
||||||
[0, 4, 1],
|
|
||||||
[1, 2, 3, 4],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
[Figures.PENTAGONAL_PYRAMID]: {
|
|
||||||
points: [
|
|
||||||
[0, 0, figureSize, 1],
|
|
||||||
[0, 100, 0, 1],
|
|
||||||
[95.1, 30.9, 0, 1],
|
|
||||||
[58.8, -80.9, 0, 1],
|
|
||||||
[-58.8, -80.9, 0, 1],
|
|
||||||
[-95.1, 30.9, 0, 1],
|
|
||||||
] as Point[],
|
|
||||||
faces: [
|
|
||||||
[0, 1, 2],
|
|
||||||
[0, 2, 3],
|
|
||||||
[0, 3, 4],
|
|
||||||
[0, 4, 5],
|
|
||||||
[0, 5, 1],
|
|
||||||
[1, 2, 3, 4, 5],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const identityMatrix: Point[] = [
|
|
||||||
[1, 0, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
const translationMatrix: Point[] = [
|
|
||||||
[1, 0, 0, 0],
|
|
||||||
[0, -1, 0, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[centerX, centerY, 0, 1],
|
|
||||||
];
|
|
||||||
|
|
||||||
// Rotate around X axis
|
|
||||||
const rotateX = (angle: number): Point[] => {
|
|
||||||
const rad = (angle * Math.PI) / 180;
|
|
||||||
const cos = Math.cos(rad);
|
|
||||||
const sin = Math.sin(rad);
|
|
||||||
|
|
||||||
return [
|
|
||||||
[1, 0, 0, 0],
|
|
||||||
[0, cos, -sin, 0],
|
|
||||||
[0, sin, cos, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rotate around Y axis
|
|
||||||
const rotateY = (angle: number): Point[] => {
|
|
||||||
const rad = (angle * Math.PI) / 180;
|
|
||||||
const cos = Math.cos(rad);
|
|
||||||
const sin = Math.sin(rad);
|
|
||||||
|
|
||||||
return [
|
|
||||||
[cos, 0, sin, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[-sin, 0, cos, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Rotate around Z axis
|
|
||||||
const rotateZ = (angle: number): Point[] => {
|
|
||||||
const rad = (angle * Math.PI) / 180;
|
|
||||||
const cos = Math.cos(rad);
|
|
||||||
const sin = Math.sin(rad);
|
|
||||||
|
|
||||||
return [
|
|
||||||
[cos, -sin, 0, 0],
|
|
||||||
[sin, cos, 0, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const rotate = (x: number, y: number, z: number): Point[] => {
|
|
||||||
return mul([rotateX(x), rotateY(y), rotateZ(z)]);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Translate
|
|
||||||
const translate = (x: number, y: number, z: number): Point[] => {
|
|
||||||
return [
|
|
||||||
[1, 0, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 0, 1, 0],
|
|
||||||
[x, y, z, 1],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Scale
|
|
||||||
const scaleMatrix = (x: number, y: number, z: number): Point[] => {
|
|
||||||
return [
|
|
||||||
[x, 0, 0, 0],
|
|
||||||
[0, y, 0, 0],
|
|
||||||
[0, 0, z, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
const projections = {
|
|
||||||
[Projections.NONE]: (): Point[] => identityMatrix,
|
|
||||||
|
|
||||||
[Projections.ISOMETRIC]: (): Point[] => [
|
|
||||||
[0.707, -0.408, 0, 0],
|
|
||||||
[0, 0.816, 0, 0],
|
|
||||||
[-0.707, -0.408, 1, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
],
|
|
||||||
|
|
||||||
[Projections.DIMETRIC]: (): Point[] => [
|
|
||||||
[0.926, 0.134, 0, 0],
|
|
||||||
[0, 0.935, 0, 0],
|
|
||||||
[0.378, -0.327, 0, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
],
|
|
||||||
|
|
||||||
[Projections.TRIMETRIC]: (): Point[] => [
|
|
||||||
[0.866, 0.354, 0, 0],
|
|
||||||
[0, 0.707, 0, 0],
|
|
||||||
[0.5, -0.612, 0, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
],
|
|
||||||
|
|
||||||
[Projections.ONE_POINT_PERSPECTIVE]: (): Point[] => [
|
|
||||||
[1, 0, 0, 0],
|
|
||||||
[0, 1, 0, 0],
|
|
||||||
[0, 0, 0, 0.001],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
],
|
|
||||||
|
|
||||||
[Projections.TWO_POINT_PERSPECTIVE]: (): Point[] => [
|
|
||||||
[1, 0, 0, 0.001],
|
|
||||||
[0, 1, 0, 0.001],
|
|
||||||
[0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1],
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Multiply array of points by matrix
|
|
||||||
const multiply = (points: Point[], matrix: Point[]): Point[] => {
|
|
||||||
const result: Point[] = [];
|
|
||||||
|
|
||||||
for (const point of points) {
|
|
||||||
const [x, y, z, w] = point;
|
|
||||||
|
|
||||||
const [x1, y1, z1, w1] = matrix[0];
|
|
||||||
const [x2, y2, z2, w2] = matrix[1];
|
|
||||||
const [x3, y3, z3, w3] = matrix[2];
|
|
||||||
const [x4, y4, z4, w4] = matrix[3];
|
|
||||||
|
|
||||||
result.push([
|
|
||||||
x * x1 + y * x2 + z * x3 + w * x4,
|
|
||||||
x * y1 + y * y2 + z * y3 + w * y4,
|
|
||||||
x * z1 + y * z2 + z * z3 + w * z4,
|
|
||||||
x * w1 + y * w2 + z * w3 + w * w4,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mul = (matrices: Point[][]) => {
|
|
||||||
let result = matrices[0];
|
|
||||||
|
|
||||||
for (let i = 1; i < matrices.length; i++) {
|
|
||||||
result = multiply(result, matrices[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw figure
|
|
||||||
const drawFigure = (points: Point[], faces: number[][]) => {
|
|
||||||
ctx.clearRect(0, 0, sizeX, sizeY);
|
|
||||||
|
|
||||||
for (const face of faces) {
|
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
for (let i = 0; i < face.length; i++) {
|
|
||||||
const [x, y] = points[face[i]];
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { currentFigure } = useFigure();
|
|
||||||
|
|
||||||
useTransformations((translation, rotation, scale, prj) => {
|
|
||||||
const matrix = mul([
|
|
||||||
figureList[currentFigure.value].points,
|
|
||||||
scaleMatrix(scale[0], scale[1], scale[2]),
|
|
||||||
rotate(rotation[0], rotation[1], rotation[2]),
|
|
||||||
translate(translation[0], translation[1], translation[2]),
|
|
||||||
projections[prj](),
|
|
||||||
]);
|
|
||||||
|
|
||||||
for (let i = 0; i < matrix.length; i++) {
|
|
||||||
matrix[i][0] = matrix[i][0] / matrix[i][3];
|
|
||||||
matrix[i][1] = matrix[i][1] / matrix[i][3];
|
|
||||||
matrix[i][2] = matrix[i][2] / matrix[i][3];
|
|
||||||
matrix[i][3] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
drawFigure(
|
|
||||||
mul([matrix, translationMatrix]),
|
|
||||||
figureList[currentFigure.value].faces
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,36 +1,31 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { HTMLElementEvent } from '@/types/dom';
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
const {
|
|
||||||
label,
|
|
||||||
min,
|
|
||||||
max,
|
|
||||||
currentValue,
|
|
||||||
defaultValue,
|
|
||||||
step = 0.1,
|
|
||||||
} = defineProps<{
|
|
||||||
label: string;
|
label: string;
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
currentValue: number;
|
currentValue: number;
|
||||||
defaultValue: number;
|
defaultValue: number;
|
||||||
step?: number;
|
step?: number;
|
||||||
}>();
|
}>(),
|
||||||
|
{
|
||||||
|
step: 0.1,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(event: 'change', value: number): void;
|
(event: 'change', value: number): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const value = ref<number>(currentValue);
|
const value = ref<number>(props.currentValue);
|
||||||
|
|
||||||
const onChange = (event: Event) => {
|
const onChange = (event: Event) => {
|
||||||
const { target } = event as HTMLElementEvent<HTMLInputElement>;
|
value.value = (event.target as HTMLInputElement).valueAsNumber;
|
||||||
value.value = target.valueAsNumber;
|
|
||||||
emit('change', value.value);
|
emit('change', value.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = () => {
|
||||||
value.value = defaultValue;
|
value.value = props.defaultValue;
|
||||||
emit('change', value.value);
|
emit('change', value.value);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -86,12 +81,12 @@ const reset = () => {
|
|||||||
|
|
||||||
&::-webkit-outer-spin-button,
|
&::-webkit-outer-spin-button,
|
||||||
&::-webkit-inner-spin-button {
|
&::-webkit-inner-spin-button {
|
||||||
-webkit-appearance: none;
|
appearance: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&[type='number'] {
|
&[type='number'] {
|
||||||
-moz-appearance: textfield;
|
appearance: textfield;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +103,7 @@ const reset = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.range__input {
|
.range__input {
|
||||||
-webkit-appearance: none;
|
appearance: none;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@@ -122,7 +117,6 @@ const reset = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
&::-webkit-slider-thumb {
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
appearance: none;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 14px;
|
height: 14px;
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { title, isActive = false } = defineProps<{
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
title?: string;
|
title?: string;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}>();
|
}>(),
|
||||||
|
{
|
||||||
|
isActive: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -30,7 +35,9 @@ const { title, isActive = false } = defineProps<{
|
|||||||
color: #67122c;
|
color: #67122c;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
transition: background-color 0.2s, transform 0.2s;
|
transition:
|
||||||
|
background-color 0.2s,
|
||||||
|
transform 0.2s;
|
||||||
|
|
||||||
&_active {
|
&_active {
|
||||||
background-color: #fdd2e2;
|
background-color: #fdd2e2;
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
<svg
|
||||||
class="w-6 h-6">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4.5 15.75l7.5-7.5 7.5 7.5"/>
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-6 h-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M4.5 15.75l7.5-7.5 7.5 7.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
<svg
|
||||||
class="w-6 h-6">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5"/>
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-6 h-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M18.75 19.5l-7.5-7.5 7.5-7.5m-6 15L5.25 12l7.5-7.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
<svg
|
||||||
class="w-6 h-6">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"/>
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-6 h-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
|
<svg
|
||||||
class="w-6 h-6">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
class="w-6 h-6"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
44
src/composables/useCanvas.ts
Normal file
44
src/composables/useCanvas.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
const updateCanvas = (
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
) => {
|
||||||
|
const centerX = width / 2;
|
||||||
|
const centerY = height / 2;
|
||||||
|
|
||||||
|
const { ctx } = initCanvas(canvas, width, height);
|
||||||
|
|
||||||
|
const { currentFigure } = useFigure();
|
||||||
|
|
||||||
|
useTransformations((translation, rotation, scale, prj) => {
|
||||||
|
const matrix = mul([
|
||||||
|
figuresMap[currentFigure.value].points,
|
||||||
|
scaleMatrix(scale[0], scale[1], scale[2]),
|
||||||
|
rotate(rotation[0], rotation[1], rotation[2]),
|
||||||
|
translate(translation[0], translation[1], translation[2]),
|
||||||
|
projectionsMap[prj](),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (let i = 0; i < matrix.length; i++) {
|
||||||
|
matrix[i][0] = matrix[i][0] / matrix[i][3];
|
||||||
|
matrix[i][1] = matrix[i][1] / matrix[i][3];
|
||||||
|
matrix[i][2] = matrix[i][2] / matrix[i][3];
|
||||||
|
matrix[i][3] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawFigure(
|
||||||
|
ctx,
|
||||||
|
mul([matrix, translationMatrix(centerX, centerY)]),
|
||||||
|
figuresMap[currentFigure.value].faces
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useCanvas = (canvas: Ref<HTMLCanvasElement | null>) => {
|
||||||
|
const { width, height } = useResizeObserver(document.body);
|
||||||
|
|
||||||
|
watch([width, height], ([width, height]) => {
|
||||||
|
if (!canvas.value) return;
|
||||||
|
updateCanvas(canvas.value, width, height);
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Types
|
||||||
export const enum Figures {
|
export const enum Figures {
|
||||||
CUBE,
|
CUBE,
|
||||||
OCTAHEDRON,
|
OCTAHEDRON,
|
||||||
@@ -6,6 +7,7 @@ export const enum Figures {
|
|||||||
PENTAGONAL_PYRAMID,
|
PENTAGONAL_PYRAMID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Composable
|
||||||
const currentFigure = ref<Figures>(Figures.PENTAGONAL_PYRAMID);
|
const currentFigure = ref<Figures>(Figures.PENTAGONAL_PYRAMID);
|
||||||
|
|
||||||
export const useFigure = () => {
|
export const useFigure = () => {
|
||||||
|
|||||||
41
src/composables/useResizeObserver.ts
Normal file
41
src/composables/useResizeObserver.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const debounce = (delay: number, fn: Function) => {
|
||||||
|
let timer: undefined | ReturnType<typeof setTimeout>;
|
||||||
|
|
||||||
|
return (...args: any[]) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
fn(...args);
|
||||||
|
}, delay);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useResizeObserver = (
|
||||||
|
element: Ref<HTMLElement | null> | HTMLElement
|
||||||
|
) => {
|
||||||
|
const width = ref(0);
|
||||||
|
const height = ref(0);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
const taget = toValue(element);
|
||||||
|
|
||||||
|
if (!taget) return;
|
||||||
|
|
||||||
|
const updateSize = debounce(100, (entries: ResizeObserverEntry[]) => {
|
||||||
|
const { width: newWidth, height: newHeight } = entries[0].contentRect;
|
||||||
|
|
||||||
|
width.value = newWidth;
|
||||||
|
height.value = newHeight;
|
||||||
|
});
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(updateSize);
|
||||||
|
|
||||||
|
observer.observe(taget);
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return { width, height };
|
||||||
|
};
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// Types
|
||||||
export const enum Projections {
|
export const enum Projections {
|
||||||
NONE,
|
NONE,
|
||||||
ISOMETRIC,
|
ISOMETRIC,
|
||||||
@@ -18,6 +19,7 @@ type fn =
|
|||||||
) => void)
|
) => void)
|
||||||
| null;
|
| null;
|
||||||
|
|
||||||
|
// Composable
|
||||||
const translation = reactive<XYZ>([0, 0, 0]);
|
const translation = reactive<XYZ>([0, 0, 0]);
|
||||||
const rotation = reactive<XYZ>([0, 0, 0]);
|
const rotation = reactive<XYZ>([0, 0, 0]);
|
||||||
const scale = reactive<XYZ>([1, 1, 1]);
|
const scale = reactive<XYZ>([1, 1, 1]);
|
||||||
|
|||||||
102
src/const/figures.ts
Normal file
102
src/const/figures.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
export const figuresMenu = [
|
||||||
|
{ figure: Figures.CUBE, title: 'Куб' },
|
||||||
|
{ figure: Figures.OCTAHEDRON, title: 'Октаэдр' },
|
||||||
|
{ figure: Figures.TRIHEDRAL_PYRAMID, title: 'Трехгранная пирамида' },
|
||||||
|
{ figure: Figures.SQUARE_PYRAMID, title: 'Четырехгранная пирамида' },
|
||||||
|
{ figure: Figures.PENTAGONAL_PYRAMID, title: 'Пятигранная пирамиида' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const figuresMap = {
|
||||||
|
[Figures.CUBE]: {
|
||||||
|
points: [
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
[0, 0, 200, 1],
|
||||||
|
[0, 200, 0, 1],
|
||||||
|
[0, 200, 200, 1],
|
||||||
|
[200, 0, 0, 1],
|
||||||
|
[200, 0, 200, 1],
|
||||||
|
[200, 200, 0, 1],
|
||||||
|
[200, 200, 200, 1],
|
||||||
|
] as Point[],
|
||||||
|
faces: [
|
||||||
|
[0, 1, 3, 2],
|
||||||
|
[0, 1, 5, 4],
|
||||||
|
[0, 2, 6, 4],
|
||||||
|
[1, 3, 7, 5],
|
||||||
|
[2, 3, 7, 6],
|
||||||
|
[4, 5, 7, 6],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
[Figures.OCTAHEDRON]: {
|
||||||
|
points: [
|
||||||
|
[0, 0, 100, 1],
|
||||||
|
[100, 100, 0, 1],
|
||||||
|
[100, -100, 0, 1],
|
||||||
|
[-100, -100, 0, 1],
|
||||||
|
[-100, 100, 0, 1],
|
||||||
|
[0, 0, -100, 1],
|
||||||
|
] as Point[],
|
||||||
|
faces: [
|
||||||
|
[0, 1, 2],
|
||||||
|
[0, 2, 3],
|
||||||
|
[0, 3, 4],
|
||||||
|
[0, 4, 1],
|
||||||
|
[5, 1, 2],
|
||||||
|
[5, 2, 3],
|
||||||
|
[5, 3, 4],
|
||||||
|
[5, 4, 1],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
[Figures.TRIHEDRAL_PYRAMID]: {
|
||||||
|
points: [
|
||||||
|
[0, 0, 100, 1],
|
||||||
|
[0, 80, 0, 1],
|
||||||
|
[86.6, -50, 0, 1],
|
||||||
|
[-86.6, -50, 0, 1],
|
||||||
|
] as Point[],
|
||||||
|
faces: [
|
||||||
|
[0, 1, 2],
|
||||||
|
[0, 2, 3],
|
||||||
|
[0, 3, 1],
|
||||||
|
[1, 2, 3],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
[Figures.SQUARE_PYRAMID]: {
|
||||||
|
points: [
|
||||||
|
[0, 0, 200, 1],
|
||||||
|
[100, 100, 0, 1],
|
||||||
|
[100, -100, 0, 1],
|
||||||
|
[-100, -100, 0, 1],
|
||||||
|
[-100, 100, 0, 1],
|
||||||
|
] as Point[],
|
||||||
|
faces: [
|
||||||
|
[0, 1, 2],
|
||||||
|
[0, 2, 3],
|
||||||
|
[0, 3, 4],
|
||||||
|
[0, 4, 1],
|
||||||
|
[1, 2, 3, 4],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
[Figures.PENTAGONAL_PYRAMID]: {
|
||||||
|
points: [
|
||||||
|
[0, 0, 200, 1],
|
||||||
|
[0, 100, 0, 1],
|
||||||
|
[95.1, 30.9, 0, 1],
|
||||||
|
[58.8, -80.9, 0, 1],
|
||||||
|
[-58.8, -80.9, 0, 1],
|
||||||
|
[-95.1, 30.9, 0, 1],
|
||||||
|
] as Point[],
|
||||||
|
faces: [
|
||||||
|
[0, 1, 2],
|
||||||
|
[0, 2, 3],
|
||||||
|
[0, 3, 4],
|
||||||
|
[0, 4, 5],
|
||||||
|
[0, 5, 1],
|
||||||
|
[1, 2, 3, 4, 5],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
55
src/const/transformations.ts
Normal file
55
src/const/transformations.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
export const axes = ['x', 'y', 'z'];
|
||||||
|
|
||||||
|
export const projectionsMenu = [
|
||||||
|
{ projection: Projections.NONE, title: 'Без проекции' },
|
||||||
|
{ projection: Projections.ISOMETRIC, title: 'Изометрическая' },
|
||||||
|
{ projection: Projections.DIMETRIC, title: 'Диметрическая' },
|
||||||
|
{ projection: Projections.TRIMETRIC, title: 'Триметрическая' },
|
||||||
|
{
|
||||||
|
projection: Projections.ONE_POINT_PERSPECTIVE,
|
||||||
|
title: 'Одноточечная перспектива',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
projection: Projections.TWO_POINT_PERSPECTIVE,
|
||||||
|
title: 'Двухточечная перспектива',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const projectionsMap = {
|
||||||
|
[Projections.NONE]: (): Point[] => identityMatrix,
|
||||||
|
|
||||||
|
[Projections.ISOMETRIC]: (): Point[] => [
|
||||||
|
[0.707, -0.408, 0, 0],
|
||||||
|
[0, 0.816, 0, 0],
|
||||||
|
[-0.707, -0.408, 1, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
],
|
||||||
|
|
||||||
|
[Projections.DIMETRIC]: (): Point[] => [
|
||||||
|
[0.926, 0.134, 0, 0],
|
||||||
|
[0, 0.935, 0, 0],
|
||||||
|
[0.378, -0.327, 0, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
],
|
||||||
|
|
||||||
|
[Projections.TRIMETRIC]: (): Point[] => [
|
||||||
|
[0.866, 0.354, 0, 0],
|
||||||
|
[0, 0.707, 0, 0],
|
||||||
|
[0.5, -0.612, 0, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
],
|
||||||
|
|
||||||
|
[Projections.ONE_POINT_PERSPECTIVE]: (): Point[] => [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 0, 0.001],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
],
|
||||||
|
|
||||||
|
[Projections.TWO_POINT_PERSPECTIVE]: (): Point[] => [
|
||||||
|
[1, 0, 0, 0.001],
|
||||||
|
[0, 1, 0, 0.001],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -1,20 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const axes = ['x', 'y', 'z'];
|
|
||||||
const projections = [
|
|
||||||
{ projection: Projections.NONE, title: 'Без проекции' },
|
|
||||||
{ projection: Projections.ISOMETRIC, title: 'Изометрическая' },
|
|
||||||
{ projection: Projections.DIMETRIC, title: 'Диметрическая' },
|
|
||||||
{ projection: Projections.TRIMETRIC, title: 'Триметрическая' },
|
|
||||||
{
|
|
||||||
projection: Projections.ONE_POINT_PERSPECTIVE,
|
|
||||||
title: 'Одноточечная перспектива',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
projection: Projections.TWO_POINT_PERSPECTIVE,
|
|
||||||
title: 'Двухточечная перспектива',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const activeProjection = ref<number>(0);
|
const activeProjection = ref<number>(0);
|
||||||
|
|
||||||
const transformations = useTransformations();
|
const transformations = useTransformations();
|
||||||
@@ -23,7 +7,7 @@ onMounted(() => (activeProjection.value = transformations.projection.value));
|
|||||||
|
|
||||||
const setActiveProjection = (index: number) => {
|
const setActiveProjection = (index: number) => {
|
||||||
activeProjection.value = index;
|
activeProjection.value = index;
|
||||||
transformations.setProjection(projections[index].projection);
|
transformations.setProjection(projectionsMenu[index].projection);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -69,7 +53,7 @@ const setActiveProjection = (index: number) => {
|
|||||||
<Accordion title="Проекции">
|
<Accordion title="Проекции">
|
||||||
<GridContainer>
|
<GridContainer>
|
||||||
<GridElement
|
<GridElement
|
||||||
v-for="(projection, i) in projections"
|
v-for="(projection, i) in projectionsMenu"
|
||||||
:key="projection.title"
|
:key="projection.title"
|
||||||
:is-active="activeProjection === i"
|
:is-active="activeProjection === i"
|
||||||
:title="projection.title"
|
:title="projection.title"
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const allFigures = [
|
|
||||||
{ figure: Figures.CUBE, title: 'Куб' },
|
|
||||||
{ figure: Figures.OCTAHEDRON, title: 'Октаэдр' },
|
|
||||||
{ figure: Figures.TRIHEDRAL_PYRAMID, title: 'Трехгранная пирамида' },
|
|
||||||
{ figure: Figures.SQUARE_PYRAMID, title: 'Четырехгранная пирамида' },
|
|
||||||
{ figure: Figures.PENTAGONAL_PYRAMID, title: 'Пятигранная пирамиида' },
|
|
||||||
];
|
|
||||||
|
|
||||||
const activeFigure = ref<number>(0);
|
const activeFigure = ref<number>(0);
|
||||||
|
|
||||||
const figure = useFigure();
|
const figure = useFigure();
|
||||||
|
|
||||||
onMounted(() => (activeFigure.value = figure.currentFigure.value));
|
onMounted(() => (activeFigure.value = figure.currentFigure.value));
|
||||||
|
|
||||||
const selectFigure = (index: number) => {
|
const selectFigure = async (index: number) => {
|
||||||
activeFigure.value = index;
|
activeFigure.value = index;
|
||||||
figure.setFigure(allFigures[index].figure);
|
figure.setFigure(figuresMenu[index].figure);
|
||||||
navigateTo('/figure');
|
await navigateTo('/figure');
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -24,7 +16,7 @@ const selectFigure = (index: number) => {
|
|||||||
<h1>Выберете фигуру</h1>
|
<h1>Выберете фигуру</h1>
|
||||||
<GridContainer>
|
<GridContainer>
|
||||||
<GridElement
|
<GridElement
|
||||||
v-for="(figure, i) in allFigures"
|
v-for="(figure, i) in figuresMenu"
|
||||||
:key="figure.title"
|
:key="figure.title"
|
||||||
:title="figure.title"
|
:title="figure.title"
|
||||||
:is-active="activeFigure === i"
|
:is-active="activeFigure === i"
|
||||||
|
|||||||
@@ -1,8 +1 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<svg height="256" width="256" xmlns="http://www.w3.org/2000/svg" viewBox="-5.8 -5.8 69.6 69.6" xml:space="preserve" stroke="#000" stroke-width=".58"><path style="fill:#67122c" d="M29 58 3 45V13l26 13z"/><path style="fill:#fae9ef" d="m29 58 26-13V13L29 26z"/><path style="fill:#fdd2e2" d="M3 13 28 0l27 13-26 13z"/></svg>
|
||||||
<svg width="256px" height="172px" viewBox="0 0 256 172" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
|
||||||
<title>NuxtJS</title>
|
|
||||||
<g>
|
|
||||||
<path d="M112.973416,9.2496789 C105.800602,-3.0832263 87.8689954,-3.0832263 80.6961815,9.2496789 L2.52446046,143.659628 C-4.64826776,155.992961 4.3176211,171.408985 18.6631204,171.408985 L79.688321,171.408985 C73.5584906,166.051862 71.2883417,156.784087 75.9271555,148.832569 L135.130926,47.3479175 L112.973416,9.2496789 Z" fill="#80EEC0"></path>
|
|
||||||
<path d="M162.504638,38.7329166 C168.440863,28.6423189 183.280784,28.6423189 189.217009,38.7329166 L253.910685,148.704498 C259.84691,158.795096 252.42695,171.408557 240.554499,171.408557 L111.167148,171.408557 C99.295126,171.408557 91.8747374,158.795096 97.8109626,148.704498 L162.504638,38.7329166 Z" fill="#00DC82"></path>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 947 B After Width: | Height: | Size: 320 B |
@@ -1,5 +0,0 @@
|
|||||||
export default defineEventHandler(() => {
|
|
||||||
return {
|
|
||||||
api: 'works',
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import http from 'k6/http';
|
|
||||||
import {sleep} from 'k6';
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
http.get('http://localhost');
|
|
||||||
sleep(1);
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export type HTMLElementEvent<T extends HTMLElement> = Event & {
|
|
||||||
target: T;
|
|
||||||
}
|
|
||||||
60
src/utils/canvas.ts
Normal file
60
src/utils/canvas.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
export function initCanvas(
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
_dpi?: number
|
||||||
|
) {
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
const bsr =
|
||||||
|
// @ts-expect-error vendor prefix
|
||||||
|
ctx.webkitBackingStorePixelRatio ||
|
||||||
|
// @ts-expect-error vendor prefix
|
||||||
|
ctx.mozBackingStorePixelRatio ||
|
||||||
|
// @ts-expect-error vendor prefix
|
||||||
|
ctx.msBackingStorePixelRatio ||
|
||||||
|
// @ts-expect-error vendor prefix
|
||||||
|
ctx.oBackingStorePixelRatio ||
|
||||||
|
// @ts-expect-error vendor prefix
|
||||||
|
ctx.backingStorePixelRatio ||
|
||||||
|
1;
|
||||||
|
|
||||||
|
const dpi = _dpi || dpr / bsr;
|
||||||
|
|
||||||
|
canvas.style.width = `${width}px`;
|
||||||
|
canvas.style.height = `${height}px`;
|
||||||
|
canvas.width = dpi * width;
|
||||||
|
canvas.height = dpi * height;
|
||||||
|
ctx.scale(dpi, dpi);
|
||||||
|
|
||||||
|
return { ctx, dpi };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function drawFigure(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
points: Point[],
|
||||||
|
faces: number[][]
|
||||||
|
) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
|
||||||
|
for (const face of faces) {
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
for (let i = 0; i < face.length; i++) {
|
||||||
|
const [x, y] = points[face[i]];
|
||||||
|
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(x, y);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
114
src/utils/transform3d.ts
Normal file
114
src/utils/transform3d.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
export type Point = [number, number, number, number];
|
||||||
|
|
||||||
|
export const identityMatrix: Point[] = [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const translationMatrix = (
|
||||||
|
centerX: number,
|
||||||
|
centerY: number
|
||||||
|
): Point[] => [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, -1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[centerX, centerY, 0, 1],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const rotateX = (angle: number): Point[] => {
|
||||||
|
const rad = (angle * Math.PI) / 180;
|
||||||
|
const cos = Math.cos(rad);
|
||||||
|
const sin = Math.sin(rad);
|
||||||
|
|
||||||
|
return [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, cos, -sin, 0],
|
||||||
|
[0, sin, cos, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rotateY = (angle: number): Point[] => {
|
||||||
|
const rad = (angle * Math.PI) / 180;
|
||||||
|
const cos = Math.cos(rad);
|
||||||
|
const sin = Math.sin(rad);
|
||||||
|
|
||||||
|
return [
|
||||||
|
[cos, 0, sin, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[-sin, 0, cos, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rotateZ = (angle: number): Point[] => {
|
||||||
|
const rad = (angle * Math.PI) / 180;
|
||||||
|
const cos = Math.cos(rad);
|
||||||
|
const sin = Math.sin(rad);
|
||||||
|
|
||||||
|
return [
|
||||||
|
[cos, -sin, 0, 0],
|
||||||
|
[sin, cos, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const rotate = (x: number, y: number, z: number): Point[] => {
|
||||||
|
return mul([rotateX(x), rotateY(y), rotateZ(z)]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Translate
|
||||||
|
export const translate = (x: number, y: number, z: number): Point[] => {
|
||||||
|
return [
|
||||||
|
[1, 0, 0, 0],
|
||||||
|
[0, 1, 0, 0],
|
||||||
|
[0, 0, 1, 0],
|
||||||
|
[x, y, z, 1],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Scale
|
||||||
|
export const scaleMatrix = (x: number, y: number, z: number): Point[] => {
|
||||||
|
return [
|
||||||
|
[x, 0, 0, 0],
|
||||||
|
[0, y, 0, 0],
|
||||||
|
[0, 0, z, 0],
|
||||||
|
[0, 0, 0, 1],
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Multiply array of points by matrix
|
||||||
|
const multiply = (points: Point[], matrix: Point[]): Point[] => {
|
||||||
|
const result: Point[] = [];
|
||||||
|
|
||||||
|
for (const point of points) {
|
||||||
|
const [x, y, z, w] = point;
|
||||||
|
|
||||||
|
const [x1, y1, z1, w1] = matrix[0];
|
||||||
|
const [x2, y2, z2, w2] = matrix[1];
|
||||||
|
const [x3, y3, z3, w3] = matrix[2];
|
||||||
|
const [x4, y4, z4, w4] = matrix[3];
|
||||||
|
|
||||||
|
result.push([
|
||||||
|
x * x1 + y * x2 + z * x3 + w * x4,
|
||||||
|
x * y1 + y * y2 + z * y3 + w * y4,
|
||||||
|
x * z1 + y * z2 + z * z3 + w * z4,
|
||||||
|
x * w1 + y * w2 + z * w3 + w * w4,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const mul = (matrices: Point[][]) => {
|
||||||
|
let result = matrices[0];
|
||||||
|
|
||||||
|
for (let i = 1; i < matrices.length; i++) {
|
||||||
|
result = multiply(result, matrices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user