Изменить размерность холста в зависимости от выбранного параметра
Я работал на холсте и сталкивался с идеей изменения размеров куба. Итак, используя HTML5 Canvas, я составил этот куб, который имеет два квадрата, соединенных линиями, чтобы сделать его похожим на куб.
Я хочу, чтобы, когда я выбираю тип куба, выберите куб, который должен автоматически меняться в зависимости от длины и ширины выбранного параметра. Высота остается постоянной. Например, если я выбираю куб 5x5, который по умолчанию является кубом, но когда я выбираю вариант 5x10, ширина (фронт) не должна изменяться, а длина (сторона) куба должна расширяться, и наоборот, если я выберите 10x5 мой максимальный вариант - 25x15. Поскольку вы можете видеть, что холст, который я создал ниже, находится в пикселях, сначала мне нужно преобразовать эти пиксели в сантиметры (см), а затем в сантиметры в кубические метры.
Весь куб должен быть выровнен в заданной области холста.
Вот скрипка
var canvas = document.querySelector('canvas');
canvas.width = 500;
canvas.height = 300;
var contxt = canvas.getContext('2d');
//squares
/*
contxt.fillRect(x, y, widht, height);
*/
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(80, 80, 100, 100);
contxt.rect(120, 40, 100, 100);
if (fillRect) {
contxt.fill();
}
contxt.stroke();
/*Lines
contxt.beginPath();
contxt.moveTo(x, y);
contxt.lineTo(300, 100);
*/
contxt.beginPath();
contxt.moveTo(80, 80);
contxt.lineTo(120, 40);
contxt.moveTo(180, 80);
contxt.lineTo(220, 40);
contxt.moveTo(80, 180);
contxt.lineTo(120, 140);
contxt.moveTo(180, 180);
contxt.lineTo(220, 140);
contxt.stroke();
canvas {
border: 1px solid #000;
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select>
<option>5x5</option>
<option>5x10</option>
<option>10x5</option>
</select>
<canvas></canvas>
Ответы
Ответ 1
Рисование куба:
Чтобы создать динамический куб, вам нужно будет прослушать событие onChange в элементе <select>
. Каждый раз, когда выбранная опция изменяется, вы захотите перерисовать свой куб.
Чтобы перерисовать куб, вам нужно создать функцию renderCube
которая должна принимать новые размеры куба и как указано смещение для позиционирования. В этой функции вы должны очистить ранее нарисованный куб и перерисовать новый с заданными размерами и смещением.
Добавление эффекта перехода:
Поскольку вы не можете применять css-переходы к элементам canvas, вам необходимо реализовать переход самостоятельно. Вам нужно будет создать функцию анимации, которая будет вычислять размеры куба на фазе перехода и повторно отображать его на экране на каждом кадре.
Реализация масштабируемого куба с эффектом перехода будет:
(если вы предпочитаете здесь тоже скрипку)
(если вам не нужен эффект перехода, проверьте скрипт, прежде чем он будет реализован)
var canvas = document.querySelector('canvas');
canvas.width = 320;
canvas.height = 150;
var contxt = canvas.getContext('2d');
var currentHeight = 0, currentWidth = 0, currentDepth = 0, animationId = 0;
function renderCube(height, width, depth, offsetX, offsetY) {
currentHeight = height;
currentWidth = width;
currentDepth = depth;
// Clear possible existing cube
contxt.clearRect(0, 0, canvas.width, canvas.height);
contxt.beginPath();
// Calculate depth, width and height based on given input
depth = (depth * 10 * 0.8) / 2;
width = width * 10;
height = height * 10;
// Draw 2 squares to the canvas
contxt.strokeStyle = 'grey';
var fillRect = false;
contxt.fillStyle = 'rgba(0, 0, 0, 0.2)';
contxt.rect(offsetX, offsetY, width, height);
contxt.rect(offsetX + depth, offsetY - depth, width, height);
if (fillRect) {
contxt.fill();
}
contxt.stroke();
// An array which specifies where to draw the depth lines between the 2 rects
// The offset will be applied while drawing the lines
var depthLineCoordinates = [
// posX, posY, posX2, posY2
[0, 0, depth, -depth],
[width, 0, width + depth, -depth],
[0, height, depth, height - depth],
[width, height, width + depth, height - depth]
];
// Draw the depth lines to the canvas
depthLineCoordinates.forEach(function(element) {
contxt.moveTo(offsetX + element[0], offsetY + element[1]);
contxt.lineTo(offsetX + element[2], offsetY + element[3]);
});
contxt.stroke();
}
// As requested by OP an example of a transition to the cube
// The transitionDuration may be a double which specifies the transition duration in seconds
function renderCubeWithTransistion(height, width, depth, offsetX, offsetY, transitionDuration) {
var fps = 60;
var then = Date.now();
var startTime = then;
var finished = false;
var heightDifference = (height - currentHeight);
var widthDifference = (width - currentWidth);
var depthDifference = (depth - currentDepth);
// Get an "id" for the current animation to prevent multiple animations from running at the same time.
// Only the last recently started animation will be executed.
// If a new one should be run, the last one will get aborted.
var transitionStartMillis = (new Date()).getMilliseconds();
animationId = transitionStartMillis;
function animate() {
// Do not continue rendering the current animation if a new one has been started
if (transitionStartMillis != animationId) return;
// request another frame if animation has not been finished
if (!finished) requestAnimationFrame(animate);
// Control FPS
now = Date.now();
elapsed = now - then;
if (elapsed > (1000 / fps)) {
then = now - (elapsed % (1000 / fps));
// Calculate a linear transition effect
if (parseInt(currentHeight, 0) != parseInt(height, 0)) currentHeight += heightDifference / (transitionDuration * fps);
if (parseInt(currentWidth, 0) != parseInt(width, 0)) currentWidth += widthDifference / (transitionDuration * fps);
if (parseInt(currentDepth, 0) != parseInt(depth, 0)) currentDepth += depthDifference / (transitionDuration * fps);
// Render the cube
renderCube(currentHeight, currentWidth, currentDepth, offsetX, offsetY);
// Check if the current dimensions of the cube are equal to the specified dimensions of the cube
// If they are the same, finish the transition
if (parseInt(currentHeight, 0) === parseInt(height, 0) && parseInt(currentWidth, 0) === parseInt(width, 0) && parseInt(currentDepth, 0) === parseInt(depth, 0)) {
finished = true;
}
}
}
// Start the animation process
animate();
return true;
}
// Draw the cube initially with 5x5
renderCube(5, 5, 5, 80, 70);
// Add the onChange event listener to the select element
var cubeSizeSelector = document.getElementById('cubeSizeSelector');
cubeSizeSelector.onchange = function(e) {
var cubeSize = e.target.value.split('x');
renderCubeWithTransistion(5, parseInt(cubeSize[0], 0), parseInt(cubeSize[1], 0), 80, 70, 0.3);
}
canvas {
border: 1px solid #000;
}
select {
display: block;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"> </script>
<select id="cubeSizeSelector">
<option>5x5</option>
<option>5x10</option>
<option>10x5</option>
</select>
<canvas></canvas>
Ответ 2
Рисование экструдированного контура. аксонометрической
В идеале вы бы создали общий аксонометрический рендерер, который, учитывая план этажа, визуализирует объект на холсте по мере необходимости.
Затем вы можете связать план с полем выбора и обновить представление, когда выбор изменился.
Лучший пример кода
В приведенном ниже примере объект renderIsoPlan
для визуализации фигуры.
Формы задаются по плану. например, поле имеет план этажа [[-1,-1],[1,-1],[1,1],[-1,1]]
представляющий 4 нижних угла.
Функция renderIsoPlan
обладает следующими свойствами:
- холст Холст, на который визуализируется форма. Не будет рисовать, пока это не будет установлено.
renderIsoPlan
создаст 2D-контекст, который будет таким же, если у вас уже есть - height Как далеко проецируется контур.
- стиль объекта стиля контекста Canvas, например
{stokeStyle: "red", lineWidth: 2}
рисует 2 пикселя с красными линиями. - план Набор точек для пола. Очки автоматически перемещаются в центр. например
[[0,-1],[1,1],[-1,1]]
рисует треугольник - не масштаб шкалы больше говорить
- Вращайте количество для поворота. Если не 0, то проекция является диметрической, иначе она триметрична.
- centerY в размерном размере холста. т.е. 0,5 является центром
- centerX такой же, как centerY
Вызов renderIsoPlan.refresh
для рисования
Обратите внимание, что вы не можете поворачивать проекцию в вопросе, поскольку она визуально выглядит деформируемой (меняет форму), поэтому, если вращение не равно 0, используется другая проекция.
Обратите внимание, что объект автоматически центрируется вокруг 0,0, используя centerX
, centerY
чтобы centerY
в представлении
setTimeout(start,0); // wait till Javascript parsed and executed
requestAnimationFrame(animate); // Animate checked at start so start anim
// named list of shapes
const boxes = {
box1By1 : {
plan : [[-1,-1],[1,-1],[1,1],[-1,1]],
scale : 35,
centerY : 0.75,
},
box1By2 : {
plan : [[-1,-2],[1,-2],[1,2],[-1,2]],
scale : 30,
centerY : 0.7,
},
box2By2 : {
plan : [[-2,-2],[2,-2],[2,2],[-2,2]],
scale : 25,
centerY : 0.7,
},
box2By1 : {
plan : [[-2,-1],[2,-1],[2,1],[-2,1]],
scale : 30,
centerY : 0.7,
},
box1By3 : {
plan : [[-1,-3],[1,-3],[1,3],[-1,3]],
scale : 22,
centerY : 0.67,
},
box1By4 :{
plan : [[-1,-4],[1,-4],[1,4],[-1,4]],
scale : 20,
centerY : 0.63,
},
lShape : {
plan : [[-2,-4],[0,-4],[0,2],[2,2],[2,4],[-2,4]],
scale : 20,
centerY : 0.65,
},
current : null,
}
// Sets the renderIsoPlan object to the current selection
function setShape(){
boxes.current = boxes[boxShape.value];
Object.assign(renderIsoPlan, boxes.current);
if (!animateCheckBox.checked) { renderIsoPlan.refresh() }
}
// When ready this is called
function start(){
renderIsoPlan.canvas = canvas;
renderIsoPlan.height = 2;
setShape();
renderIsoPlan.refresh();
}
// Add event listeners for checkbox and box selection
boxShape.addEventListener("change", setShape );
animateCheckBox.addEventListener("change",()=>{
if (animateCheckBox.checked) {
requestAnimationFrame(animate);
} else {
renderIsoPlan.rotate = 0;
setShape();
}
});
// Renders animated object
function animate(time){
if (animateCheckBox.checked) {
renderIsoPlan.rotate = time / 1000;
renderIsoPlan.refresh();
requestAnimationFrame(animate);
}
}
// Encasulate Axonometric render.
const renderIsoPlan = (() => {
var ctx,canvas,plan,cx,cy,w,h,scale,height, rotate;
height = 50;
scale = 10;
rotate = 0;
const style = {
strokeStyle : "#000",
lineWidth : 1,
lineJoin : "round",
lineCap : "round",
};
const depthScale = (2/3);
// Transforms then projects the point to 2D
function transProjPoint(p) {
const project = rotate !== 0 ? 0 : depthScale;
const xdx = Math.cos(rotate);
const xdy = Math.sin(rotate);
const y = p[0] * xdy + p[1] * xdx;
const x = p[0] * xdx - p[1] * xdy - y * project;
return [x,y * depthScale];
}
// draws the plan
function draw() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0,0,w,h);
ctx.setTransform(scale, 0, 0, scale, cx, cy);
var i = plan.length;
ctx.beginPath();
while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
ctx.closePath();
i = plan.length;
ctx.translate(0,-height);
ctx.moveTo(...transProjPoint(plan[--i]))
while(i--){ ctx.lineTo(...transProjPoint(plan[i])) }
ctx.closePath();
i = plan.length;
while(i--){
const [x,y] = transProjPoint(plan[i]);
ctx.moveTo(x,y);
ctx.lineTo(x,y + height);
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.stroke();
}
// centers the plan view on coordinate 0,0
function centerPlan(plan){
var x = 0, y = 0;
for(const point of plan){
x += point[0];
y += point[1];
}
x /= plan.length;
y /= plan.length;
for(const point of plan){
point[0] -= x;
point[1] -= y;
}
return plan;
}
// Sets the style of the rendering
function setStyle(){
for(const key of Object.keys(style)){
if(ctx[key] !== undefined){
ctx[key] = style[key];
}
}
}
// define the interface
const API = {
// setters allow the use of Object.apply
set canvas(c) {
canvas = c;
ctx = canvas.getContext("2d");
w = canvas.width; // set width and height
h = canvas.height;
cx = w / 2 | 0; // get center
cy = h / 2 | 0; // move center down because plan is extruded up
},
set height(hh) { height = hh },
set style(s) { Object.assign(style,s) },
set plan(points) { plan = centerPlan([...points]) },
set scale(s) { scale = s },
set rotate(r) { rotate = r },
set centerY(c) { cy = c * h },
set centerX(c) { cx = c * w },
// getters not used in the demo
get height() { return height },
get style() { return style },
get plan() { return plan },
get scale() { return scale },
get rotate() { return r },
get centerY() { return cy / h },
get centerX() { return cx / w },
// Call this to refresh the view
refresh(){
if(ctx && plan){
ctx.save();
if(style){ setStyle() }
draw();
ctx.restore();
}
}
}
// return the interface
return API;
})();
canvas { border : 2px solid black; }
<select id="boxShape">
<option value = "box1By1">1 by 1</option>
<option value = "box1By2">1 by 2</option>
<option value = "box2By2">2 by 2</option>
<option value = "box2By1">2 by 1</option>
<option value = "box1By3">1 by 3</option>
<option value = "box1By4">1 by 4</option>
<option value = "lShape">L shape</option>
</select>
<input type="checkBox" id="animateCheckBox" checked=true>Animate</input><br>
<canvas id="canvas"></canvas>