1
0
mirror of https://github.com/robonen/canvas-3d.git synced 2026-03-20 02:44:40 +00:00

feat(engine): all figures and transformations ready

This commit is contained in:
2022-11-27 08:23:17 +07:00
parent ae9661f4b1
commit 77b552a310
7 changed files with 262 additions and 69 deletions

View File

@@ -21,47 +21,116 @@ onMounted(() => {
const centerX = sizeX / 2; const centerX = sizeX / 2;
const centerY = sizeY / 2; const centerY = sizeY / 2;
const figureSizeH = 200; const figureSize = 200;
const figureSizeW = 200;
const points: Point[] = [ const figureList = {
[0, 0, figureSizeH, 1], [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], [0, 100, 0, 1],
[95.1, 30.9, 0, 1], [95.1, 30.9, 0, 1],
[58.8, -80.9, 0, 1], [58.8, -80.9, 0, 1],
[-58.8, -80.9, 0, 1], [-58.8, -80.9, 0, 1],
[-95.1, 30.9, 0, 1], [-95.1, 30.9, 0, 1],
]; ] as Point[],
faces: [
const faces = [
[0, 1, 2], [0, 1, 2],
[0, 2, 3], [0, 2, 3],
[0, 3, 4], [0, 3, 4],
[0, 4, 5], [0, 4, 5],
[0, 5, 1], [0, 5, 1],
[1, 2, 3, 4, 5], [1, 2, 3, 4, 5],
],
},
};
const identityMatrix: Point[] = [
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
]; ];
// Cube const translationMatrix: Point[] = [
// const points: Point[] = [ [1, 0, 0, 0],
// [0, 0, 0, 1], [0, -1, 0, 0],
// [0, 0, 100, 1], [0, 0, 1, 0],
// [0, 100, 0, 1], [centerX, centerY, 0, 1],
// [0, 100, 100, 1], ];
// [100, 0, 0, 1],
// [100, 0, 100, 1],
// [100, 100, 0, 1],
// [100, 100, 100, 1],
// ];
//
// const 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],
// ];
// Rotate around X axis // Rotate around X axis
const rotateX = (angle: number): Point[] => { const rotateX = (angle: number): Point[] => {
@@ -129,13 +198,43 @@ onMounted(() => {
]; ];
}; };
const axonometryIsometric = (): Point[] => { const projections = {
return [ [Projections.NONE]: (): Point[] => identityMatrix,
[Projections.ISOMETRIC]: (): Point[] => [
[0.707, -0.408, 0, 0], [0.707, -0.408, 0, 0],
[0, 0.816, 0, 0], [0, 0.816, 0, 0],
[-0.707, -0.408, 1, 0], [-0.707, -0.408, 1, 0],
[0, 0, 0, 1], [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 // Multiply array of points by matrix
@@ -193,18 +292,27 @@ onMounted(() => {
} }
}; };
useTransformations((translation, rotation, scale) => { const { currentFigure } = useFigure();
drawFigure(
mul([ useTransformations((translation, rotation, scale, prj) => {
points, const matrix = mul([
figureList[currentFigure.value].points,
scaleMatrix(scale[0], scale[1], scale[2]), scaleMatrix(scale[0], scale[1], scale[2]),
rotate(rotation[0], rotation[1], rotation[2]), rotate(rotation[0], rotation[1], rotation[2]),
translate(translation[0], translation[1], translation[2]), translate(translation[0], translation[1], translation[2]),
rotateX(-90), projections[prj](),
axonometryIsometric(), ]);
translate(centerX, centerY + figureSizeH / 2, 0),
]), for (let i = 0; i < matrix.length; i++) {
faces 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
); );
}); });
}); });

View File

@@ -5,12 +5,14 @@ const {
label, label,
min, min,
max, max,
currentValue,
defaultValue, defaultValue,
step = 0.1, step = 0.1,
} = defineProps<{ } = defineProps<{
label: string; label: string;
min: number; min: number;
max: number; max: number;
currentValue: number;
defaultValue: number; defaultValue: number;
step?: number; step?: number;
}>(); }>();
@@ -19,7 +21,7 @@ const emit = defineEmits<{
(event: 'change', value: number): void; (event: 'change', value: number): void;
}>(); }>();
const value = ref<number>(defaultValue); const value = ref<number>(currentValue);
const onChange = (event: Event) => { const onChange = (event: Event) => {
const { target } = event as HTMLElementEvent<HTMLInputElement>; const { target } = event as HTMLElementEvent<HTMLInputElement>;

View File

@@ -0,0 +1,20 @@
export const enum Figures {
CUBE,
OCTAHEDRON,
TRIHEDRAL_PYRAMID,
SQUARE_PYRAMID,
PENTAGONAL_PYRAMID,
}
const currentFigure = ref<Figures>(Figures.PENTAGONAL_PYRAMID);
export const useFigure = () => {
const setFigure = (figure: Figures) => {
currentFigure.value = figure;
};
return {
currentFigure,
setFigure,
};
};

View File

@@ -1,10 +1,27 @@
export const enum Projections {
NONE,
ISOMETRIC,
DIMETRIC,
TRIMETRIC,
ONE_POINT_PERSPECTIVE,
TWO_POINT_PERSPECTIVE,
}
export type XYZ = [number, number, number]; export type XYZ = [number, number, number];
type fn = ((translation: XYZ, rotation: XYZ, scale: XYZ) => void) | null; type fn =
| ((
translation: XYZ,
rotation: XYZ,
scale: XYZ,
projection: Projections
) => void)
| null;
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]);
const projection = ref<Projections>(Projections.NONE);
const setTranslation = (axis: number, value: number) => { const setTranslation = (axis: number, value: number) => {
translation[axis] = value; translation[axis] = value;
@@ -18,17 +35,23 @@ const setScale = (axis: number, value: number) => {
scale[axis] = value; scale[axis] = value;
}; };
const setProjection = (value: Projections) => {
projection.value = value;
};
export const useTransformations = (onUpdate: fn = null) => { export const useTransformations = (onUpdate: fn = null) => {
watchEffect(() => { watchEffect(() => {
if (onUpdate) onUpdate(translation, rotation, scale); if (onUpdate) onUpdate(translation, rotation, scale, projection.value);
}); });
return { return {
translation, translation,
rotation, rotation,
scale, scale,
projection,
setTranslation, setTranslation,
setRotation, setRotation,
setScale, setScale,
setProjection,
}; };
}; };

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
const showMenu = ref<boolean>(false); const showMenu = ref<boolean>(true);
</script> </script>
<template> <template>

View File

@@ -1,17 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
const axes = ['x', 'y', 'z']; const axes = ['x', 'y', 'z'];
const projections = [ 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();
onMounted(() => (activeProjection.value = transformations.projection.value));
const setActiveProjection = (index: number) => {
activeProjection.value = index;
transformations.setProjection(projections[index].projection);
};
</script> </script>
<template> <template>
@@ -20,9 +33,10 @@ const transformations = useTransformations();
<FormRange <FormRange
v-for="(axis, i) in axes" v-for="(axis, i) in axes"
:label="`${axis} =`" :label="`${axis} =`"
:min="-100" :min="-500"
:max="100" :max="500"
:step="1" :step="5"
:current-value="transformations.translation[i]"
:defaultValue="0" :defaultValue="0"
@change="transformations.setTranslation(i, $event)" @change="transformations.setTranslation(i, $event)"
/> />
@@ -34,6 +48,7 @@ const transformations = useTransformations();
:min="0" :min="0"
:max="359" :max="359"
:step="1" :step="1"
:current-value="transformations.rotation[i]"
:defaultValue="0" :defaultValue="0"
@change="transformations.setRotation(i, $event)" @change="transformations.setRotation(i, $event)"
/> />
@@ -46,6 +61,7 @@ const transformations = useTransformations();
:min="0.1" :min="0.1"
:max="5" :max="5"
:step="0.1" :step="0.1"
:current-value="transformations.scale[i]"
:defaultValue="1" :defaultValue="1"
@change="transformations.setScale(i, $event)" @change="transformations.setScale(i, $event)"
/> />
@@ -54,10 +70,10 @@ const transformations = useTransformations();
<GridContainer> <GridContainer>
<GridElement <GridElement
v-for="(projection, i) in projections" v-for="(projection, i) in projections"
:key="projection" :key="projection.title"
:is-active="activeProjection === i" :is-active="activeProjection === i"
:title="projection" :title="projection.title"
@click="activeProjection = i" @click="setActiveProjection(i)"
/> />
</GridContainer> </GridContainer>
</Accordion> </Accordion>

View File

@@ -1,10 +1,34 @@
<script setup lang="ts"> <script setup lang="ts">
const figures = ['Тетраэдр', 'Гексаэдр', 'Октаэдр', 'Додекаэдр', 'Икосаэдр']; 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 figure = useFigure();
onMounted(() => (activeFigure.value = figure.currentFigure.value));
const selectFigure = (index: number) => {
activeFigure.value = index;
figure.setFigure(allFigures[index].figure);
navigateTo('/figure');
};
</script> </script>
<template> <template>
<h1>Выберете фигуру</h1> <h1>Выберете фигуру</h1>
<GridContainer> <GridContainer>
<GridElement v-for="figure in figures" :key="figure" :title="figure" /> <GridElement
v-for="(figure, i) in allFigures"
:key="figure.title"
:title="figure.title"
:is-active="activeFigure === i"
@click="selectFigure(i)"
/>
</GridContainer> </GridContainer>
</template> </template>