I am trying to transform point cloud for register.
I have got quaternion, but when I do the transformation using point cloud library. It seem to transform point cloud in local coordinate instead of world coordinate. So, it can't do the register. I want to know the formula of Quaternion convert to rotation matrix
https://pointclouds.org/documentation/tutorials/matrix_transform.html#
Here is my transformation code
float qw, qx, qy, qz, tx, ty, tz;
qw = kinectmatrix4f[num][0];
qx = kinectmatrix4f[num][1];
qy = kinectmatrix4f[num][2];
qz = kinectmatrix4f[num][3];
tx= kinectmatrix4f[num][4];
ty = kinectmatrix4f[num][5];
tz = kinectmatrix4f[num][6];
//qx = -qx;
qy = -qy;
//init transformation matrix
Eigen::Matrix4f transform_matrix = Eigen::Matrix4f::Identity();
transform_matrix <<
1 - 2 * pow(qy, 2) - 2 * pow(qz, 2), 2 * qx*qy - 2 * qz*qw, 2 * qx*qz + 2 * qy*qw, 0,
2 * qx*qy + 2 * qz*qw, 1 - 2 * pow(qx, 2) - 2 * pow(qz, 2), 2 * qy*qz - 2 * qx*qw, 0,
2 * qx*qz - 2 * qy*qw, 2 * qy*qz + 2 * qx*qw, 1 - 2 * pow(qx, 2) - 2 * pow(qy, 2), 0,
0, 0, 0, 1;
printf("Transforming point cloud %i in rough\n", num);
std::cout << transform_matrix << std::endl;
pcl::transformPointCloud(*cloud_input, *cloud_input, transform_matrix);
However, I try transformation in unity by this code. It seems to change rotation well.
Quaternion rot=new Quaternion(0.4f,0.5f,0.9f,1);
transform.rotation=rot;
I'm not quite sure what you are asking for. But If you are using Eigen here.
You can simply convert quaternion rotation to rotation matrix using Eigen. Quaternionf.toRotationMatrix()
Eigen::Quaternionf quat(qw, qx, qy, qz);
//init transformation matrix
Eigen::Matrix4f transform_matrix = Eigen::Matrix4f::Identity();
// convert Quaternion to rotation matrix
Matrix3f rot_mat = quat.toRotationMatrix();
// assign the rotation part of transform matrix
transform_matrix.block(0, 0, 3, 3) = rot_mat;
// you should create another point cloud to acquire your result
pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_cloud(new pcl::PointCloud <pcl::PointXYZ>)
pcl::PointCloud<pcl::PointXYZ>::Ptr transformed_cloud2(new pcl::PointCloud <pcl::PointXYZ>)
// do transform
pcl::transformPointCloud(*cloud_input, *transformed_cloud, transform_matrix);
// if above transformed_cloud is not what you expected, try inverse the transform matrix to check if your rotation is "back-ward"
pcl::transformPointCloud(*cloud_input, *transformed_cloud2, transform_matrix.inverse());
My current program draw a rotated bitmap (64x64) and tile it on the screen by drawing it again but adding an offset based on the computed position of the bitmap top right corner (after rotation), it works fine but i experience some jerkiness of the grid in motion.
The jerkiness doesn't appear if i do the same thing with canvas transforms.
Here is an example which compare both : https://editor.p5js.org/onirom/sketches/A5D-0nxBp
Move mouse to the left part of the canvas for the custom rotation algorithm and to the right part for the canvas one.
It seems that some tile are misplaced by a single pixel which result in the grid jerkiness.
Is there a way to remove the grid jerkiness without doing it as a single pass and keeping the same interpolation scheme ?
Is it a sub pixels correctness issue ?
Here is some code :
let tileImage = null
function preload() {
tileImage = loadImage('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAMeHpUWHRSYXcgcHJvZmlsZSB0eXBlIGV4aWYAAHjarZhpktw6DoT/8xRzBO7LcbhGzA3m+PMBlKq77eeIN0tXuKSiKBIEkImEzf7XP4/5B38hhmhiKjW3nC1/scXmOzfV3r+h385G/da/+Dzi949x83ngGQpcw/3Z/DO+GefePb/bs4l7578LvTeuc5e+HvT+jI+f4+NZ0NdfF3osCO7ubNfzwrNQ8I9Fj+nzsSi3Wn4cbc1n5/gM1a9/MRSfU3Yl8h29LSU37qu3seDPJYaGottb8+70Dry/36kem/wOLli+Q3isDPLPh864fNtQjUxkQpcQ8p2CU8dbQokJLNzuwqfbjzO/++bLR3/4+zvHsmxy9s0J8+vKv+bN5879YfxJg0/Uan4ehJ9htflz/ctxl96F3gfhs4//vnOdn51/jK/y5L19ffQt3OesevTQnKLHjC/yc6j3KHrHvCFe1Lcyn2KzIWsrN/JpfKrtdpICy052HNw354n9cdEt191xW6/TTUyMfvvC1ftpfNDBSpCan2SE00SI7vgSWlihkh5TcygG/7HF6bZNt5uu2mXsckz1jsWcJtl/+TF/d+I5giXnbP34Cru8oAErrCP8cmEaEXHncWpSB7+fX/8kroEIJnVz5YDdDnOXGMl9JVfQQAcmJq4Xe66sZwFcxNYJY1wgAja7kFzGouJ9cQ5HVgLUMd2H6AcRcCn5hZE+hpAJDuhgb94pTqf65O8wrArVAtMcCrFpoROsGBP5U2Ilh3oKKaaUciqpppZ6DlmQl3PJQs+9hBJLKrmUUk1ppddQY00111JrbbU33wL0nRo4bbW11jubdlbuvN2Z0PvwI4w40sijjDqaGX2SPjPONPMss842+/IrLAC+8iqrrrb6dptU2nGnnXfZdbfdD6l2woknnXyKOfW00z9Re8L62+c/iJp7ouY1UjKxfKLGaCnvEk7oJEnMiJiPjoAXokbESGyJma0uRi+Rk5hRj0BF8hiZJDjLScSIYNzOp+M+sXsiZ3z//8TNlKpx8/9r5IyE7m9G7ve4/VXUllSJqRG7MBSn2gD6Zuh+1MxXOwvzGokTjw8Dm0KanWNTDswM4gpmJheTS1Ss7mufpXOK7fOeBbP6STudmOyu6/TaUjpe5/lRoIY1/TC19p0zX3jIYnfPx/eG7bIA5UfWHHXGuNrQrUpvBMfXxS+mow3kPhnxWyoONxQ5c1+hTzyUWwwt75xCS+y3wPEcu/aSfDthDQg6nhb3Fhsca5h2Mu4LJ7cdSwnIJbvYeM8xZ1i9NIfDoWExhnoAD+TriMSijUqJr/yyybgkkSFAva3ZC8nIU/UfySirEylxox99yDGJfXGhrordfsQ9sq8lDGs44NwJv+2O5WmvMCsZoDbkxuHTnHFVCcCUc7s+7LoR8VRsLBPXxWKCK5OEFdcVwkqubs4/yZVWWAZvimnY0uwNvoYPJxDKuBapGJ3HAJPvC2uyFMl0mh2rndYmBCqjBcAlx7HBWA5BDKl92QrmRnjONsLOZmARyUDmT52zc+zk96q8DigOIZ5eFpSEBBXIkG1TCQ6DkClk32jUSRZa2JZmlPfibqCQm0bpjolX5+ZVlwdDUYyts3Nm+CDr6TC7ILPIkG1i68PJwcg+9s8UTPB4d5Ml0WyjVcIxMEgzZnfOzVBhVcIomdBcNKnAJ0SH9YmaHg9vj7aw5DG0phgI2YEZur+jHAij0aOhXfgkszehYf5gN8LWWsp1T/yM1fiZS25S4NhHfLRbHp5qVZECzQo+2anHCrGd55cochyi99SFPAVWFYDHvElTEPdZClqsvdYkAZezydXkUQtIJZW7Te+onrzWDfmO3ppT9FIIkSFd3HVDj6AEB7EJ+Dvl6D6pOL3GBHirxFhxn+Abe3/hCZ8RO2qwr8OmKXHdHHOA9tAN5FglTzziSTlKqAoXnYud75FhvUF8IKWdxSOS24kUshSLZYRTJNsaQSZ8TEyZgJFm1U9CqY6CsUaSofrFdM3pd6wjSiaa7Y4ASkPYs+tBdk1tSeQ+4yX1lYg1BlKMOBcJKTvClaw9BTemdnSvJEYSqXs5Bnf0bVuDYFyqB3ynOpKUIpJFiInKkDE/VeHju5m5tMWOrlIoiJ4TY+Ilrj4KQI0f7gGzrIVSvQzDnqO4O50OEta4mJYmTF9ooWT7eR2Wgiwv4cn7saDB9CGze433PMZ+HeyaEduAz+P1govTft/l+xW+lhz1Qhk+G9AcyccsbOXEgdBGmRA9/KnUcWBOPyDMTF4hJa4hER5bSZyiGHPHAFXED8QoWQMRjFSfrS77UHQ94ZSUEJAnSiMT7sYsRWQpXGDaKKgfeySfFemUD7cJ1DxLuWvDh2tF6jXKQ4NDCpPiu+H8SVmpy8wPdmBQYIJ+yNdrCFg9RpXrndJEJuUnNqgsQULvUh+ngdJG0+ylPvBhSxSMg/QexClWQYRgbMDILVMYs5AirpthD8HsDAZWcEFAvxE8i1jd75JipKEhl4SYpGbG2Z0UKWKhJeSqAWlfYY/kTR9PhWVH8e2IYhCZsyQXOE5pc3Pki3FBMKCOEkyqplSymyZkNhBSJYIlIj5sI/MknRgjo6AbzUTlD/+m3HUOdNohHaoBEsM0qZrjVqOK36nmTlwzBOIU61SdDx+OxbH0jvQYRYiEI190piH9GhpFthaoNz2T8ARKEId0qsiE3QDuw+E9Sif4KZRzvvAyf0TAe10i2ORFirWTCD25khflpNUXSEbWVUkk3Ou6/Q2nGolc9kJajMNN1iBK6ChZMaKUyRRrJJ3GSYGMqP0IlDq5UahQZdgTvsZRoJSU2saJ5EvIwOrKDLAGAgzrRXSh/fEMdy1pyXg0uvWaKujoKzbExWN6kK3YUCeNYVr3X0pGbZbsFAXi4PJdU+iFHRA+T70XPHKA6qZVXYVjpPAY2MLJlleViVbET9MnzgCqE++dFY89z0MyYFEdTvTwLScX2aziVprjW+66VrjuIayE4q/sRNDfyrU5ZoQpQs/NQwui2bus26hhFv43SpeLekCfui/aXZWKlB+lEwrSq463BHEyhGYV+lNFsUvSWmBEt+GDJFr0B545NxsNdp974AZsziL1gUZsYYgB89FiZGo021+BrzVBpCXrBodkC6Cii/lNGKvp+KN8WI5HSL350TnF5EMWXDzQQJUgloSPQifTaLWg3jabaGJhYaC6T5anoqVG8cS0ZSO9yV77I/JgDM6x0CCXZx9vgYYsWGsflr1JQENllQHV2c57LbtAX/qOvCExDiKMKV1AovlQklJRQnl0YEOUJMIs4RNp0bIhUbuYSm+gDQzJtaSaIAfFBVokxUg3dsKncLo4RvR+V1GCpBVbKEcxCMfJXlJsRhYtxVGla+Ji+4KBvC3hSsVXV4imkHwniE5hY9R2Uhl1pLpgPol4yGEEGbEeq6eRUADaDuFmUl+fUvBqAfC8lYBIDKtkLai5izJc5DvPJlWQ2IoluAF/OKodOpAYgXF3eS6q4KY3d0SNwsAalI6pFRi2BnQOHhzu4WX8RDNWOErYZfyh7TK3+ri4eMdDeAp+myTOWOWon7DF7Ff+3QpUVjqoAw6540dSmbyhzElAyKCnB/baC9KrScv11SMGaY7qbYwfugW+0rlkSrTRIqBCrDjAA0TcQm5KP+VEEQxhpyMuUv5DH+HCoG0pOYXHSX+3oze6+7feDElZaaG/DT+9liiZQnD9fGtid1csqjOlzZJUXyJ7pdtWmR0byYhjLiYe5s35uCA8L7Qk3NskmnsMdRvKH5jyRPa/7ZN4Gye+r7XntfqyueTjINsAY1kWioNybX/K0YBIohc5KFmg+YMb12yktYgjoVtyUHokLTQue0n5/OVMo95kN7SW/CdXxItZm3uvXZxiVjqtywIglCC7IL0SCb61iNOvoI8QYjDO+CHQ+hVoTtrFV8wB+JyUt4qIp4ug29SukPs0BD1Ley1SBnUJfKko3aFsb2iGhEZlGQ6KUzqiR8qSc5z6drveG4a70EZE6NNnCcsAe9GZrhZACLESRuEOUUmPkKGVIh+rSFGshzPwkVvKuqpP54LutlDzpVnVdLsfeIQf0FVfSnf6nwf1mhztvZr3JlpZUjvuIysPabOWek8A9ghClRo7ZEEOpy1xqEP8RLFRhHJ9hSc04X7uJNdnvkvyvz0zP828+FvOSWtKy2YEiWsWf8rcdIvULpG3IHhtwSR9lZ3iB6FPF7Uxul1cz1GdrZUjbKMQZmZZbaFuSNgUyNmzmrXm3yxc9DLeW7anAAABhWlDQ1BJQ0MgcHJvZmlsZQAAeJx9kT1Iw0AcxV9bpUUqDhYRcchQHaQFURFHrUIRKoRaoVUHk0u/oElDkuLiKLgWHPxYrDq4OOvq4CoIgh8gjk5Oii5S4v+SQosYD4778e7e4+4d4G9UmGp2jQOqZhnpZELI5laF4CvCCGEAMYxJzNTnRDEFz/F1Dx9f7+I8y/vcn6NXyZsM8AnEs0w3LOIN4ulNS+e8TxxhJUkhPieOGXRB4keuyy6/cS467OeZESOTnieOEAvFDpY7mJUMlXiKOKqoGuX7sy4rnLc4q5Uaa92TvzCc11aWuU5zGEksYgkiBMiooYwKLMRp1Ugxkab9hId/yPGL5JLJVQYjxwKqUCE5fvA/+N2tWZiccJPCCaD7xbY/RoDgLtCs2/b3sW03T4DAM3Cltf3VBjDzSXq9rUWPgL5t4OK6rcl7wOUOMPikS4bkSAGa/kIBeD+jb8oB/bdAz5rbW2sfpw9AhrpK3QAHh8BokbLXPd4d6uzt3zOt/n4Aru1yvz0Dqp8AAA0aaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjNlYzk5MTljLWI0OTUtNGExMC1hNTQyLWI1NjQ4ZDc1YzcwYSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpmMDczODg5NS1mZDBmLTRjMGUtOTRjOS0yZjA4YjZiYjVjYjEiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxNjkwYmU3NS02NzE1LTQwMDEtYTdkZS0xMjBjODQ3MjU3MzgiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzA1Mzk0OTE3Nzc3MTUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMGQ2NWZlOS1lMDQ1LTRhOGItYWZmZS0xZGRjMWI0M2Q3MTciCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTA4VDIzOjQ0OjUxKzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnF4ZZ8AAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfmDAgWLDOSx0KrAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAABDVJREFUeNrtmzFrFVEQhSeSwiIEIhJFEUUMQVERBFNYCGKhiJWllVjbCPojLGysLS2tREghgoVFBEFEEIkERRQNYiCksNPGr3jHPW/uJi8iZqZZdt/eve/tnDlzZu59YzevXvgZHbay+iNabGpye0RE7J2eiIiIew9fDXx++/qZiIh48vzjup6vdvTQzs55rl06HhERn5bXho5n3oU3nyMiYltscRs7dmS2EwFzh/cMHfjh62pERHz5PvyN794xMXC+f9fkwHjOQRIe4lzt5eJy07ytVghwCNgsU0T0tVF5vhDw28bxCG+W2NcYdRygsZ3dxzwOCfocxl88fbDzflifLOSygOOOQoDLs8rCsDOfc3Q2e2Cq8/qd+y8GPHF+btCzeBIjX6un3f1nT+3rnJfrb9+vDOiIQoDmfY0hzcuv330bmqcx7sMun5uJiIgTM9MRETG/sNb5fOZXpTi/sNSJGMcJTk+ABDioEOA0vfO044ZMyRF7LsadlgeZ3AcSVKm2KkkMJBYCsiqv1dPu3LF1Vq05HaHX0Q2uusyuFwLIxyurkwOeUmWVxVhrDDp90LfK5HNVlhki4KLqBygH8EbUk6r4WmMZ7Q3bqgfUHj1b2lC1p+O0puB3qD6pfkBrP0Crxv/FigNcp0bZta/nWzs/60XUqBBZCKC6gt2zPK324PHiQBbR5/XVAZotYG2eXxywWRyA4tNqLEMGdT4eylZ8XJWZKUytCltjP+OKQkCm3UGEeo5xIEC1PJ7CAyhC7TtoreFMkTaqLFEI0BjkDbu1QVcjZHmeHqAa84AAagjV9HSTN6o7CgEOAcq2rabr/qzgaHfW1flZTLeyfbbi5DihEKA9N2Lb6QHNCjqeul5Z3/X2Rm0OMaUDNtoP+Fds1H2J4gB9o8qi2tPD3CqsVnPUENoL5DpZhBUfTKvKP5Vo914jOEznhbt0nkKA++DGlZOdnuNNav5Xw3Pc56pKkIRnFIHaxdWdHtzv1iKZj6OOLwQQO7wRF/PZvoGs7ueco8aqruyoQnV7fPT7qgLlHKShVGuHCAjgDbdWU+oJ2DpbhVWk4BnXaXI1hH5PN2/WX6gsAAJgZ1iY/OqMmOM+5wHtEjtEaJbRvK77F521erwQoAhQdndVmyo/p/R4HrEKZ7idpGSFTNu7DhRsrr1H17fQjlNVg1oNZtnA7eXN6m+39vi3q7/igKwabN2bM6q6Xp+70f8TuPHVEXIIoOpz9XxmsL7T6tr3Vx2QrT26bKP/GtNaQles1G7dfVoIiIgYd3W9ru+rh/AE+oCjruBo/gYBenRdaIcE17niedqHqCzQtyOkHnHr+lpvOzbXzk4rtzhE6P8PtSZRhPG9VdEWAvr+h1fvdxodJKgn3W7y7HsoAlSBuue68YWAjAP6GiytypI3rzHZ17SnqL29iOkmTlH9sOUR8AvuXvZlpzhIWwAAAABJRU5ErkJggg==')
}
function setup() {
createCanvas(512, 512)
frameRate(14)
tileImage.loadPixels()
}
function computeRotatedPoint(c, s, x, y) {
return { x: x * c - y * s, y: x * s + y * c }
}
currentTileWidth = 0
currentTileHeight = 0
// draw a rotated bitmap at screen position ox, oy
function drawRotatedBitmap(c, s, ox, oy) {
let dcu = s
let dcv = c
let dru = dcv
let drv = -dcu
let su = (tileImage.width / 2.0) - (currentTileWidth_d2 * dcv + currentTileHeight_d2 * dcu)
let sv = (tileImage.height / 2.0) - (currentTileWidth_d2 * drv + currentTileHeight_d2 * dru)
let ru = su
let rv = sv
for (let y = 0; y < currentTileHeight; y += 1) {
let u = ru
let v = rv
for (let x = 0; x < currentTileWidth; x += 1) {
let ui = u
let vi = v
if (ui >= 0 && ui < tileImage.width) {
let index1 = (floor(ui) + floor(vi) * tileImage.width) * 4
let index2 = (x + ox + (y + oy) * width) * 4
pixels[index2 + 0] = tileImage.pixels[index1 + 0]
pixels[index2 + 1] = tileImage.pixels[index1 + 1]
pixels[index2 + 2] = tileImage.pixels[index1 + 2]
}
u += dru
v += drv
}
ru += dcu
rv += dcv
}
}
let angle = 0
function draw() {
background(0)
const s = sin(angle / 256 * PI * 2)
const c = cos(angle / 256 * PI * 2)
// compute rotated tile width / height
let tw = tileImage.width
let th = tileImage.height
if (angle % 128 < 64) {
currentTileWidth = abs(tw * c + th * s)
currentTileHeight = abs(tw * s + th * c)
} else {
currentTileWidth = abs(tw * c - th * s)
currentTileHeight = abs(tw * s - th * c)
}
currentTileWidth_d2 = (currentTileWidth / 2.0)
currentTileHeight_d2 = (currentTileHeight / 2.0)
// compute rotated point
const rp = computeRotatedPoint(c, s, tw, 0)
// draw tiles
loadPixels()
for (let i = -3; i <= 3; i += 1) {
// compute center
const cx = width / 2 - currentTileWidth_d2
const cy = height / 2 - currentTileHeight_d2
// compute tile position
const ox = rp.x * i
const oy = rp.y * i
drawRotatedBitmap(c, s, round(cx + ox), round(cy + oy))
}
updatePixels()
angle += 0.5
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<main>
</main>
</body>
</html>
I have found a solution which does not use the same algorithm but use the same interpolation scheme.
Solution with a three-shear method
This solution use a three-pass shear method and the solution to fix the tiles jerkiness is to add the tile offset before the rotation and then round coordinates once everything is ready to be drawn :
/**
* Bitmap rotation + stable tiling with 3-shearing method
* The 3-shearing method is stable between -PI / 2 and PI / 2 only, that is why a flip is needed for a full rotation
*
* https://www.ocf.berkeley.edu/~fricke/projects/israel/paeth/rotation_by_shearing.html
*/
let tex = null
let tile = []
function preload() {
tile = loadImage('data:image/png;base64,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W2sfpw9AhrpK3QAHh8BokbLXPd4d6uzt3zOt/n4Aru1yvz0Dqp8AAA0aaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjNlYzk5MTljLWI0OTUtNGExMC1hNTQyLWI1NjQ4ZDc1YzcwYSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpmMDczODg5NS1mZDBmLTRjMGUtOTRjOS0yZjA4YjZiYjVjYjEiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxNjkwYmU3NS02NzE1LTQwMDEtYTdkZS0xMjBjODQ3MjU3MzgiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzA1Mzk0OTE3Nzc3MTUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMGQ2NWZlOS1lMDQ1LTRhOGItYWZmZS0xZGRjMWI0M2Q3MTciCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTA4VDIzOjQ0OjUxKzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnF4ZZ8AAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfmDAgWLDOSx0KrAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAABDVJREFUeNrtmzFrFVEQhSeSwiIEIhJFEUUMQVERBFNYCGKhiJWllVjbCPojLGysLS2tREghgoVFBEFEEIkERRQNYiCksNPGr3jHPW/uJi8iZqZZdt/eve/tnDlzZu59YzevXvgZHbay+iNabGpye0RE7J2eiIiIew9fDXx++/qZiIh48vzjup6vdvTQzs55rl06HhERn5bXho5n3oU3nyMiYltscRs7dmS2EwFzh/cMHfjh62pERHz5PvyN794xMXC+f9fkwHjOQRIe4lzt5eJy07ytVghwCNgsU0T0tVF5vhDw28bxCG+W2NcYdRygsZ3dxzwOCfocxl88fbDzflifLOSygOOOQoDLs8rCsDOfc3Q2e2Cq8/qd+y8GPHF+btCzeBIjX6un3f1nT+3rnJfrb9+vDOiIQoDmfY0hzcuv330bmqcx7sMun5uJiIgTM9MRETG/sNb5fOZXpTi/sNSJGMcJTk+ABDioEOA0vfO044ZMyRF7LsadlgeZ3AcSVKm2KkkMJBYCsiqv1dPu3LF1Vq05HaHX0Q2uusyuFwLIxyurkwOeUmWVxVhrDDp90LfK5HNVlhki4KLqBygH8EbUk6r4WmMZ7Q3bqgfUHj1b2lC1p+O0puB3qD6pfkBrP0Crxv/FigNcp0bZta/nWzs/60XUqBBZCKC6gt2zPK324PHiQBbR5/XVAZotYG2eXxywWRyA4tNqLEMGdT4eylZ8XJWZKUytCltjP+OKQkCm3UGEeo5xIEC1PJ7CAyhC7TtoreFMkTaqLFEI0BjkDbu1QVcjZHmeHqAa84AAagjV9HSTN6o7CgEOAcq2rabr/qzgaHfW1flZTLeyfbbi5DihEKA9N2Lb6QHNCjqeul5Z3/X2Rm0OMaUDNtoP+Fds1H2J4gB9o8qi2tPD3CqsVnPUENoL5DpZhBUfTKvKP5Vo914jOEznhbt0nkKA++DGlZOdnuNNav5Xw3Pc56pKkIRnFIHaxdWdHtzv1iKZj6OOLwQQO7wRF/PZvoGs7ueco8aqruyoQnV7fPT7qgLlHKShVGuHCAjgDbdWU+oJ2DpbhVWk4BnXaXI1hH5PN2/WX6gsAAJgZ1iY/OqMmOM+5wHtEjtEaJbRvK77F521erwQoAhQdndVmyo/p/R4HrEKZ7idpGSFTNu7DhRsrr1H17fQjlNVg1oNZtnA7eXN6m+39vi3q7/igKwabN2bM6q6Xp+70f8TuPHVEXIIoOpz9XxmsL7T6tr3Vx2QrT26bKP/GtNaQles1G7dfVoIiIgYd3W9ru+rh/AE+oCjruBo/gYBenRdaIcE17niedqHqCzQtyOkHnHr+lpvOzbXzk4rtzhE6P8PtSZRhPG9VdEWAvr+h1fvdxodJKgn3W7y7HsoAlSBuue68YWAjAP6GiytypI3rzHZ17SnqL29iOkmTlH9sOUR8AvuXvZlpzhIWwAAAABJRU5ErkJggg==')
}
function setup() {
createCanvas(512, 512)
frameRate(12)
}
function computeRotatedPoint(c, s, x, y) {
return { x: x * c - y * s, y: x * s + y * c }
}
function _shearX(t, x, y) {
return x - y * t
}
function _shearY(s, x, y) {
return x * s + y
}
currentTileWidth = 0
currentTileHeight = 0
currentTileLookupFunction = tileLookup1
// regular lookup
function tileLookup1(x, y) {
return (x + y * tile.width) * 4
}
// flipped lookup
function tileLookup2(x, y) {
return ((tile.width - 1 - x) + (tile.height - 1 - y) * tile.width) * 4
}
// draw a rotated bitmap at offset ox,oy with cx,cy as center of rotation offset
function drawRotatedBitmap(c, s, t, ox, oy, cx, cy) {
for (let ty = 0; ty < tile.height; ty += 1) {
for (let tx = 0; tx < tile.width; tx += 1) {
// center of rotation
let scx = tile.width - tx - tile.width / 2
let scy = tile.height - ty - tile.height / 2
// this is key to a stable rotation without any jerkiness
scx += cx
scy += cy
// shear
let ux = round(_shearX(t, scx, scy))
let uy = round(_shearY(s, ux, scy))
ux = round(_shearX(t, ux, uy))
// translate again
ux = currentTileWidth_d2 - ux
uy = currentTileHeight_d2 - uy
// plot with offset
let index1 = currentTileLookupFunction(tx, ty)
let index2 = (ox + ux + (oy + uy) * width) * 4
pixels[index2 + 0] = tile.pixels[index1 + 0]
pixels[index2 + 1] = tile.pixels[index1 + 1]
pixels[index2 + 2] = tile.pixels[index1 + 2]
}
}
}
let angle = -3.141592653 / 2
function draw() {
const s = sin(angle)
const c = cos(angle)
const t = tan(angle / 2)
tile.loadPixels()
background(0)
// compute rotated tile width / height
let tw = tile.width
let th = tile.height
currentTileWidth = abs(tw * c + th * s)
currentTileHeight = abs(tw * s + th * c)
currentTileWidth_d2 = round(currentTileWidth / 2.0)
currentTileHeight_d2 = round(currentTileHeight / 2.0)
// draw tiles
loadPixels()
for (let j = -2; j <= 2; j += 1) {
for (let i = -2; i <= 2; i += 1) {
let ox = round(width / 2 - currentTileWidth_d2)
let oy = round(height / 2 - currentTileHeight_d2)
drawRotatedBitmap(c, s, t, ox, oy, i * tw, j * tw)
}
}
updatePixels()
angle += 0.025
if (angle >= PI / 2) {
angle -= PI
if (currentTileLookupFunction === tileLookup2) {
currentTileLookupFunction = tileLookup1
} else {
currentTileLookupFunction = tileLookup2
}
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/addons/p5.sound.min.js"></script>
<link rel="stylesheet" type="text/css" href="style.css">
<meta charset="utf-8" />
</head>
<body>
<main>
</main>
</body>
</html>
I cannot say technically why it works but it is probably related to an error accumulation issue / rounding since i can reproduce the question issue completely with the three-shear method if i add the tile offset after rotation and round the offset and shear pass independently such as :
function drawRotatedBitmap(c, s, t, ox, oy, cx, cy) {
cx = round(_shearX(t, cx, cy))
cy = round(_shearY(s, cx, cy))
cx = round(_shearX(t, cx, cy))
for (let ty = 0; ty < tex.height; ty += 1) {
...
let ux = round(_shearX(t, scx, scy))
let uy = round(_shearY(s, ux, scy))
ux = round(_shearX(t, ux, uy))
...
let index2 = (cx + ux + ox + (cy + uy + oy) * width) * 4
...
}
}
The issue become clearly visible if i round the offset and the shearing result at the same time which result in missing pixels in the final image such as :
function drawRotatedBitmap(c, s, t, ox, oy, cx, cy) {
cx = _shearX(t, cx, cy)
cy = _shearY(s, cx, cy)
cx = _shearX(t, cx, cy)
for (let ty = 0; ty < tex.height; ty += 1) {
...
let ux = _shearX(t, scx, scy)
let uy = _shearY(s, ux, scy)
ux = _shearX(t, ux, uy)
...
let index2 = (round(cx + ux) + ox + (round(cy + uy) + oy) * width) * 4
...
}
}
I would still like a detailed explanation of the jerkiness behavior and to know if there is a smooth solution by adding the tile offset after the rotation, it seems that the jiggling is due to the center of rotation being off one or two pixels depending on the angle.
This is definitely a pixelization problem. Analytically (vectorial) one can't explain the jiggling. It can be minimized, e.g. by rotating around the center of the whole image the successive pixels of a line and so forth line-by-line of the whole image, but the jiggling cannot be cancelled. Ultimately, this corresponds to creating an image object and rotating it around its center!
Is there a way to remove the grid jerkiness without doing it as a single pass and keeping the same interpolation scheme ?
No
Is it a sub pixels correctness issue ?
No, it is an interpolation issue
The frameCount is a function of the frameRate (which is not a constant contrary to what we think even if we set it), the execution time of the cycling draw function and the runtime environment (canvas or the gif one). It seems that for the gif the frameCount is reset to zero after a certain cumulated count, which corresponds to a reset to the vertical position of the image.
I tried to reproduce the "jerking effect" by changing the 64 parameter in the following instruction and the frameRate, without success.
if (frameCount % 128 < 64) {
I suggest to make the rotation speed independent of the frameCount.
FWIW the WEBGL canvas will be a bit faster in getting a result your code already hints at using image() and because you're using power of 2 dimensions you could also make use of textureWrap():
// this variable will hold our shader object
let bricks;
function preload() {
bricks = loadImage(
"data:image/png;base64,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W2sfpw9AhrpK3QAHh8BokbLXPd4d6uzt3zOt/n4Aru1yvz0Dqp8AAA0aaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/Pgo8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA0LjQuMC1FeGl2MiI+CiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIKICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpHSU1QPSJodHRwOi8vd3d3LmdpbXAub3JnL3htcC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjNlYzk5MTljLWI0OTUtNGExMC1hNTQyLWI1NjQ4ZDc1YzcwYSIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpmMDczODg5NS1mZDBmLTRjMGUtOTRjOS0yZjA4YjZiYjVjYjEiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxNjkwYmU3NS02NzE1LTQwMDEtYTdkZS0xMjBjODQ3MjU3MzgiCiAgIGRjOkZvcm1hdD0iaW1hZ2UvcG5nIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJMaW51eCIKICAgR0lNUDpUaW1lU3RhbXA9IjE2NzA1Mzk0OTE3Nzc3MTUiCiAgIEdJTVA6VmVyc2lvbj0iMi4xMC4zMCIKICAgdGlmZjpPcmllbnRhdGlvbj0iMSIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuMTAiPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMGQ2NWZlOS1lMDQ1LTRhOGItYWZmZS0xZGRjMWI0M2Q3MTciCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkdpbXAgMi4xMCAoTGludXgpIgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTA4VDIzOjQ0OjUxKzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAKPD94cGFja2V0IGVuZD0idyI/PnF4ZZ8AAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfmDAgWLDOSx0KrAAAAGXRFWHRDb21tZW50AENyZWF0ZWQgd2l0aCBHSU1QV4EOFwAABDVJREFUeNrtmzFrFVEQhSeSwiIEIhJFEUUMQVERBFNYCGKhiJWllVjbCPojLGysLS2tREghgoVFBEFEEIkERRQNYiCksNPGr3jHPW/uJi8iZqZZdt/eve/tnDlzZu59YzevXvgZHbay+iNabGpye0RE7J2eiIiIew9fDXx++/qZiIh48vzjup6vdvTQzs55rl06HhERn5bXho5n3oU3nyMiYltscRs7dmS2EwFzh/cMHfjh62pERHz5PvyN794xMXC+f9fkwHjOQRIe4lzt5eJy07ytVghwCNgsU0T0tVF5vhDw28bxCG+W2NcYdRygsZ3dxzwOCfocxl88fbDzflifLOSygOOOQoDLs8rCsDOfc3Q2e2Cq8/qd+y8GPHF+btCzeBIjX6un3f1nT+3rnJfrb9+vDOiIQoDmfY0hzcuv330bmqcx7sMun5uJiIgTM9MRETG/sNb5fOZXpTi/sNSJGMcJTk+ABDioEOA0vfO044ZMyRF7LsadlgeZ3AcSVKm2KkkMJBYCsiqv1dPu3LF1Vq05HaHX0Q2uusyuFwLIxyurkwOeUmWVxVhrDDp90LfK5HNVlhki4KLqBygH8EbUk6r4WmMZ7Q3bqgfUHj1b2lC1p+O0puB3qD6pfkBrP0Crxv/FigNcp0bZta/nWzs/60XUqBBZCKC6gt2zPK324PHiQBbR5/XVAZotYG2eXxywWRyA4tNqLEMGdT4eylZ8XJWZKUytCltjP+OKQkCm3UGEeo5xIEC1PJ7CAyhC7TtoreFMkTaqLFEI0BjkDbu1QVcjZHmeHqAa84AAagjV9HSTN6o7CgEOAcq2rabr/qzgaHfW1flZTLeyfbbi5DihEKA9N2Lb6QHNCjqeul5Z3/X2Rm0OMaUDNtoP+Fds1H2J4gB9o8qi2tPD3CqsVnPUENoL5DpZhBUfTKvKP5Vo914jOEznhbt0nkKA++DGlZOdnuNNav5Xw3Pc56pKkIRnFIHaxdWdHtzv1iKZj6OOLwQQO7wRF/PZvoGs7ueco8aqruyoQnV7fPT7qgLlHKShVGuHCAjgDbdWU+oJ2DpbhVWk4BnXaXI1hH5PN2/WX6gsAAJgZ1iY/OqMmOM+5wHtEjtEaJbRvK77F521erwQoAhQdndVmyo/p/R4HrEKZ7idpGSFTNu7DhRsrr1H17fQjlNVg1oNZtnA7eXN6m+39vi3q7/igKwabN2bM6q6Xp+70f8TuPHVEXIIoOpz9XxmsL7T6tr3Vx2QrT26bKP/GtNaQles1G7dfVoIiIgYd3W9ru+rh/AE+oCjruBo/gYBenRdaIcE17niedqHqCzQtyOkHnHr+lpvOzbXzk4rtzhE6P8PtSZRhPG9VdEWAvr+h1fvdxodJKgn3W7y7HsoAlSBuue68YWAjAP6GiytypI3rzHZ17SnqL29iOkmTlH9sOUR8AvuXvZlpzhIWwAAAABJRU5ErkJggg=="
);
}
function setup() {
// use WEBGL renderer,
createCanvas(512, 512, WEBGL);
// if helps the sketch dimensions are a power of 2 so textureWrap() can be used
textureWrap(REPEAT);
noStroke();
}
function draw() {
background(0);
// full 7 block width and height
const w = 64 * 7;
const h = 64;
// half the dimensions to rotate from center
const hw = int(w / 2);
const hh = int(h / 2);
rotate(frameCount * 0.03);
texture(bricks);
// vertex( x, y, u, v ) (by default u,v are in pixel coordinates)
// otherwise use textMode(NORMAL); in setup()
beginShape();
vertex(-hw, -hh, -hw, -hh); //TL
vertex(+hw, -hh, +hw, -hh); //TR
vertex(+hw, +hh, +hw, +hh); //BR
vertex(-hw, +hh, -hw, +hh); //BL
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
An overcomplicated version of the above is to use a shader for rotation which can produce interesting results:
It seems you're more interested in working out a custom rotation algorithms that produces less artefacts.
If so, you might also want to look at a RotSprite.
Doing a quick search I see implementations such as this shader one or this js one.
I am working on Three.js to build a terrain as showed below.
const segment = 25
const geometry = new THREE.PlaneGeometry(dimension, dimension, segment, segment)
for (let i = 0; i < vertices; i++) {
const idx = i * vertices
const fVal = (simplex.noise2D(ni, nj) + 1) / 2
const sVal = (0.5 * simplex.noise2D(ni * 2, nj * 2) + 1) / 2
const tVal = (0.25 * simplex.noise2D(ni * 4, nj * 4) + 1) / 2
const vtx = (fVal + sVal + tVal) / 2.15
geometry.attributes.position.setY(idx, Math.pow(vtx, 2.5) * 350)
}
The application throws out an error saying: TypeError: Cannot read properties of undefined (reading 'position'), which points out geometry.attributes is null.
My three.js version is 0.124 and currently I don't want to update it to 0.125 since there are many features relying on current version.
How would I solve this issue.
Update, at last I update it to 0.125 version, and it works
Is it possible to change the value of an attribute in the shader and have this reflected in the buffer for the next frame render?
So for example change the position of a vertex in the vertex shader and send this new value back to the javascript buffer object?
Code sample below:
attribute vec3 newPosition;
attribute vec3 divideVal;
void main() {
vec3 difference = newPosition - position;
velocity = difference / divideVal;
position += velocity;
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = size * (sizeMultipler / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
Edit:
Right now I do this in the JS itself, but understand it will be faster if I move as much calculation as I can to the shaders? This is my current JS:
const positions = this.geometry.attributes.position.array;
const newPositions = this.geometry.attributes.newPosition.array;
for (let i = 0, i3 = 0; i < this.numParticles; i++, i3 += 3) {
const velocity = [newPositions[i3] - positions[i3], newPositions[i3 + 1] - positions[i3 + 1], newPositions[i3 + 2] - positions[i3 + 2]];
if (velocity[0] || velocity[1] || velocity[2]) {
const minReset = 1;
velocity[0] = velocity[0] / 60;
velocity[1] = velocity[1] / 60;
velocity[2] = velocity[2] / 60;
positions[i3] = positions[i3] + velocity[0];
positions[i3 + 1] = positions[i3 + 1] + velocity[1];
positions[i3 + 2] = positions[i3 + 2] + velocity[2];
}
}
I found out how to do this.
Using a concept called FBO Simulation, I created a simulation shader, which computes the calculations in its glsl shaders, and then rather than displaying the results on screens, writes them to a texture. I then read from that texture in the "real" shader and wrote the results to the screen. This also allowed me to compare different output textures to work out velocity and size differences of particles between frames.
You can read more about it being discussed here: https://github.com/mrdoob/three.js/issues/1183
Example:
http://barradeau.com/blog/?p=621
I use this code to draw triangles in Cesium:
var mypositions = Cesium.Cartesian3.fromDegreesArrayHeights(triangles);
// unroll 'mypositions' into a flat array here
var numPositions = mypositions.length;
var pos = new Float64Array(numPositions * 3);
var normals = new Float32Array(numPositions * 3);
for (var i = 0; i < numPositions; ++i) {
pos[i * 3] = mypositions[i].x;
pos[i * 3 + 1] = mypositions[i].y;
pos[i * 3 + 2] = mypositions[i].z;
normals[i * 3] = 0.0;
normals[i * 3 + 1] = 0;
normals[i * 3 + 2] = 1.0;
}
console.log(normals)
var geometry = new Cesium.Geometry({
vertexFormat : Cesium.VertexFormat.ALL,
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE, // not FLOAT
componentsPerAttribute: 3,
values: pos
}),
normal: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 3,
values: normals
})
},
// Don't need the following line if no vertices are shared.
// indices: new Uint32Array([0, 1, 2, 3, 4, 5]),
primitiveType: Cesium.PrimitiveType.TRIANGLES,
boundingSphere: Cesium.BoundingSphere.fromVertices(pos)
});
var myInstance = new Cesium.GeometryInstance({
geometry: geometry,
attributes: {
color: new Cesium.ColorGeometryInstanceAttribute(0.0039215697906911,
0.7333329916000366,
0,
1)
},
show: new Cesium.ShowGeometryInstanceAttribute(true)
});
var TIN = viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: [myInstance],
asynchronous: false,
appearance: new Cesium.PerInstanceColorAppearance({
closed: true,
translucent: false,
flat: false
//,
//vertexShaderSource: "",
//fragmentShaderSource: ""
})
}));
This is what I get:
I would like to enable shading, so the result should be similar as on figure below:
I tried to write Vertex and Fragment GLSL shader but without success. I am not familiar with GLSL and I was getting a compiling error. Is there any another way to create this kind of shading?
Thanks!
Regardless of the fact that you haven't posted your GLSL shaders or gotten that to work, your problem (once you eventually figure out the GLSL stuff) is that you're setting all your normals to point in the +Z direction instead of actually being normal to each triangle, like the second screenshot of yours shows.
var normals = new Float32Array(numPositions * 3);
for (var i = 0; i < numPositions; ++i) {
pos[i * 3] = mypositions[i].x;
pos[i * 3 + 1] = mypositions[i].y;
pos[i * 3 + 2] = mypositions[i].z;
normals[i * 3] = 0.0;
normals[i * 3 + 1] = 0;
normals[i * 3 + 2] = 1.0;
}
What you need to do instead is set the positions and then for the normals, operate on sets of 3 vertices (a triangle) instead of individual vertices. This way you can actually calculate a surface normal. This is one of many explanations of how to do that:
https://math.stackexchange.com/questions/305642/how-to-find-surface-normal-of-a-triangle
I'm not familiar with Cesium, but I'd imagine there'd be some "default" shader that did basic lighting. If not, then the basis for simple lighting like this is called Lambertian reflectance. Your vertex shader would output a color that is calculated as dot(N, L) where N is the normal of your vertex and L is the vector from that vertex to your light source (or just the negative of the direction of your light if it's a directional/environment/sun/etc light). The fragment shader would simply pass that color back out.