STACK Question - 3d-cube-transformations
STACK Question 8 Interpretation
Two figures showing a 3D object and a rotation and/or translation of the same object. Enter a matrix which transforms the first figure into the second.
- figure 1: First impression of the question |
Question description
The user is given a skyblue cube, they need to transform it into the green cube. The cube transforms when the user updates the input fields.
Student perspective
- figure 2: When the user updates the input fields |
When the user updates the input field the cube is transformed. If an input field is left empty, it is filled with the value ‘0’. This is because sometimes users forget something and the cube disapears because all inputs must be filled.
- figure 3: When the user submits the question |
When the user submits the question, STACK highlights the wrong matrix entries in red
Question code
Question variables
/* generate a random list of numbers [x,y,z] between a given range*/
matrix_position:
matrixmap(lambda([ex], float(rand_with_prohib(-5, 5, [0]))), zeromatrix(1, 3));
matrix_scale:
matrixmap(lambda([ex], float(rand_with_prohib(1,10, [0])/10)), zeromatrix(1, 3));
matrix_rotation:
matrixmap(lambda([ex], float(rand_with_prohib(0,359,[500]))), zeromatrix(1, 3));
/*create a list of of the random numbers from matrices*/
DPosition: makelist(matrix_position[1][i], i, 1, 3);
DScale: makelist(matrix_scale[1][i], i, 1, 3);
DRotation: makelist(matrix_rotation[1][i], i, 1, 3);
/* Default task cube parameters */
MPosition:DPosition;
MScale:DScale;
MRotation:DRotation;
color:"green";
/*input this variable into the model answer if its not */
M:matrix(MPosition,MScale,MRotation);
/* User controlled cube parameters */
MPosition_user:[-2,-1,-2];
MScale_user:[0.5,0.4,0.7];
MRotation_user:[45,0,0];
color_user:"skyblue";
Question Text
<p id="p">Transform the {#color_user#} cube into the {#color#} </p>
[[jsxgraph height='850px' width='850px']]
//remove qoutation marks from html<p> element
var p =document.getElementById("p");
p.innerHTML = p.innerHTML.replace(/"/g, '');
//element id's list and select which inputs to change id
var elementId =["x","y","z","scaleX","scaleY","scaleZ","rotX","rotY","rotZ"];
var classNames =["position","scale","rotate"];
var mainElement =document.getElementsByClassName("matrixtable")[0];
var elements = mainElement.getElementsByTagName("input");
//create a jsxgraph board
var board = JXG.JSXGraph.initBoard(divid,{
boundingbox: [-8, 8, 8, -8],
keepaspectratio: false,
axis: false,
zoom: {
factorX: 1.1,
factorY: 1.1,
wheel: true,
needshift: true,
eps: 0.5
}
});
//create a 3D view
var view = board.create('view3d',
[[-6, -3], [8, 8],
[[-5, 5], [-5, 5], [-5, 5]]],
{
xPlaneRear: {visible: false},
yPlaneRear: {visible: false},
zPlaneRear: {visible: false}
});
var points =[];
var faces = [];
var namesr =["A","B","C","D","E","F","G","H","i"];
//create rotation matrices geometry related
function getRotationMatrices(rotation) {
var radX = -rotation[0] * Math.PI / 180;
var radY = -rotation[1] * Math.PI / 180;
var radZ = -rotation[2] * Math.PI / 180;
var xRotation = [
[1, 0, 0],
[0, Math.cos(radX), -Math.sin(radX)],
[0, Math.sin(radX), Math.cos(radX)],
];
var yRotation = [
[Math.cos(radY), 0, Math.sin(radY)],
[0, 1, 0],
[-Math.sin(radY), 0, Math.cos(radY)],
];
var zRotation = [
[Math.cos(radZ), -Math.sin(radZ), 0],
[Math.sin(radZ), Math.cos(radZ), 0],
[0, 0, 1],
];
return { xRotation, yRotation, zRotation };
}
//function to rotate a cube/matrix
function rotateMatrix(pointCoords, position, rotMat, rotation) {
//set cube coordinates to (0,0,0) in order to rotate the cube
for (var i = 0; i < pointCoords.length; i++) {
pointCoords[i][0] = pointCoords[i][0] - position[0];
pointCoords[i][1] = pointCoords[i][1] - position[1];
pointCoords[i][2] = pointCoords[i][2] - position[2];
}
//apply matrix rotations
var rotXYZ = JXG.Math.matMatMult(rotMat.xRotation, rotMat.yRotation);
rotXYZ = JXG.Math.matMatMult(rotXYZ, rotMat.zRotation);
pointCoords = JXG.Math.matMatMult(pointCoords, rotXYZ);
//set cube coordinates back to user provided coordinates
for (var i = 0; i < pointCoords.length; i++) {
pointCoords[i][0] = pointCoords[i][0] + position[0];
pointCoords[i][1] = pointCoords[i][1] + position[1];
pointCoords[i][2] = pointCoords[i][2] + position[2];
}
return pointCoords;
}
// creates a 3D cube
function create3DCube(position , scale, rotation, color) {
//require x,y, and z coordinates from user (may remove)
if (!Array.isArray(position) || position.length !== 3) {
throw new Error('The "coordinates" parameter must be an array with three elements.');
}
// give defualt values for optional parameters
rotation = rotation || [0, 0, 0];
position = position || [0, 0, 0];
scale = scale || [0.5, 0.5, 0.5];
color = color || "blue";
// attributes for the points, and faces
var point_attr = { withLabel:false,fixed:true,fillColor:'red',label:{offset:[5, 5]}},
pol_attr = { borders: { strokeWidth: 0.5 }, fillColor: color},
faces = [];
points = [];
var rotMat =getRotationMatrices(rotation)
//all 8 points for cube
var phi = (1 + Math.sqrt(5)) * 0.5;
var pointCoords = [
[position[0]-phi*scale[0], position[1]-phi*scale[1], position[2]-phi*scale[2]],
[position[0]-phi*scale[0], position[1]+phi*scale[1], position[2]-phi*scale[2]],
[position[0]+phi*scale[0], position[1]+phi*scale[1], position[2]-phi*scale[2]],
[position[0]+phi*scale[0], position[1]-phi*scale[1], position[2]-phi*scale[2]],
[position[0]-phi*scale[0], position[1]-phi*scale[1], position[2]+phi*scale[2]],
[position[0]-phi*scale[0], position[1]+phi*scale[1], position[2]+phi*scale[2]],
[position[0]+phi*scale[0], position[1]+phi*scale[1], position[2]+phi*scale[2]],
[position[0]+phi*scale[0], position[1]-phi*scale[1], position[2]+phi*scale[2]]
];
//we have to transalte the cube to origin, rotate
//then translate back to desired user position
//otherwise the cube will move around the rotation axis.
//translate points to origin
pointCoords = rotateMatrix(pointCoords,position,rotMat,rotation);
// Create the points for the cube
for (var i = 0; i < 8; i++) {
point_attr = {fixed:true,size:2, name:namesr[i], label: { offset: [5, 5]}}
var point = view.create('point3d', pointCoords[i], point_attr);
points.push(point);
}
// coordinates for cube faces
var facesArray = [
[0, 1, 2, 3],
[4, 5, 6, 7],
[0, 1, 5, 4],
[2, 3, 7, 6],
[1, 2, 6, 5],
[0, 3, 7, 4]
];
//create cube faces
for (var i = 0; i < facesArray.length; i++) {
faces.push(board.create(
'polygon',[points[facesArray[i][0]],points[facesArray[i][1]],
points[facesArray[i][2]],points[facesArray[i][3]]],{fillColor:color, borders:
{ strokeWidth: 0.5 }}));
}
var cubeProperties = {
pointCoords: pointCoords,
points: points,
faces: faces,
scale: scale,
rotation: rotation,
position: position,
color: color,
// transforms the 3Dcube
transform: function(newPosition, newScale, newRotation, newColor) {
this.position = newPosition || this.position;
this.scale = newScale || this.scale;
this.rotation = newRotation || this.rotation;
this.color = newColor || this.color;
//remove previous points and faces
board.removeObject(this.points);
for (var i = 0; i < this.faces.length; i++) {
board.removeObject(this.faces[i]);
}
//create points and faces for new cube
var newCube = create3DCube(this.position, this.scale, this.rotation, this.color);
this.pointCoords = newCube.pointCoords;
this.points = newCube.points;
this.faces = newCube.faces;
}
};
return cubeProperties;
}
var cube2 =
create3DCube({#MPosition#},{#MScale#},{#MRotation#},{#color#});
var cube1 =
create3DCube({#MPosition_user#}, {#MScale_user#}, {#MRotation_user#},{#color_user#});
//debounce function for input optimization
const debounce = (func, delay) => {
var timerId;
return function(...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
func(...args);
timerId = null;
}, delay);
};
};
// get the cube matrix values
var position =cube1.position;
var scale=cube1.scale;
var rotation =cube1.rotation;
for (var f=0; f<3;f++) {
// sets starting input values equal to cube value here
elements[f].value=position[f];
elements[f+3].value=scale[f];
elements[f+6].value= rotation[f];
// give inputs appropriate classNames
elements[f].className=classNames[0];
elements[f+3].className=classNames[1];
elements[f+6].className=classNames[2];
}
//change inputfield id, and add function to handle user input
for(var z=0; z< elements.length;z++) {
//give inputs ids
elements[z].id=elementId[z];
elements[z].placeholder=elementId[z];
//function to transform cube when input changes
elements[z].addEventListener('input', debounce(function(e) {
//if input is empty set it to 0
if (!e.target.value) {
e.target.value = 0;
}
//get new cube coordinates
for(var i=0; i<3;i++) {
position[i] = parseFloat(document.getElementById(elementId[i]).value);
scale[i] = parseFloat(document.getElementById(elementId[i+3]).value);
rotation[i] = parseFloat(document.getElementById(elementId[i+6]).value);
}
//apply new coordinates
cube1.transform(position,scale,rotation);
},500));
}
[[/jsxgraph]]
<p>[[input:ans1]] [[validation:ans1]]</p>