37 subscribers
5 photos
2 videos
1 file
13 links
Please insert disk 2...
Download Telegram
و نتیجه کار در نهایت شد ویدئو بعدی
کاری که امروز میخوایم بکنیم ساخت یک مکعب 3بعدی در حال چرخش هست.
بریم که مراحل مطالعات لازم رو براش شروع کنیم
خب اول از همه میرسیم به تبدیل مختصات سه‌بعدی که باید انجام بدیم و چیزی که با سرچ بدست آوردم و از صفحه wikipedia پیدا کردم این هست!
حالا با کمک maple و ضربشون در ماتریس [i,k,j] به ماتریس زیر رسیدم...
Vector[row](3, [(i*cos(B) - (-j*sin(A) + k*cos(A))*sin(B))*cos(C) + (j*cos(A) + k*sin(A))*sin(C), -(i*cos(B) - (-j*sin(A) + k*cos(A))*sin(B))*sin(C) + (j*cos(A) + k*sin(A))*cos(C), i*sin(B) + (-j*sin(A) + k*cos(A))*cos(B)])
CD
حالا با کمک maple و ضربشون در ماتریس [i,k,j] به ماتریس زیر رسیدم... Vector[row](3, [(i*cos(B) - (-j*sin(A) + k*cos(A))*sin(B))*cos(C) + (j*cos(A) + k*sin(A))*sin(C), -(i*cos(B) - (-j*sin(A) + k*cos(A))*sin(B))*sin(C) + (j*cos(A) + k*sin(A))*cos(C), i*sin(B)…
تابع calculateX در این برنامه، مسئول محاسبه مختصات جدید x یک نقطه در فضای سه‌بعدی بعد از چرخش آن حول محورهای مختلف (چرخش در فضای سه‌بعدی) است. این تابع با استفاده از زوایای چرخش
A، B و C (به ترتیب حول محورهایx، y و z)، مختصات جدید x را محاسبه می‌کند.
این تابع سه ورودی دارد که مختصات اصلی یک نقطه در فضای سه‌بعدی هستند:

i: مختصه‌ی x
j: مختصه‌ی y
k: مختصه‌ی z

تابع calculateX به همراه توابع مشابه دیگر (calculateY و calculateZ) مسئول انجام چرخش سه‌بعدی بر روی مختصات مکعب است تا این مکعب به صورت چرخان در صفحه‌نمایش دو‌بعدی ظاهر شود.

float calculateX(int i, int j, int k) {
return j * sin(A) * sin(B) * cos(C) - k * cos(A) * sin(B) * cos(C) +
j * cos(A) * sin(C) + k * sin(A) * sin(C) + i * cos(B) * cos(C);
}

float calculateY(int i, int j, int k) {
return j * cos(A) * cos(C) + k * sin(A) * cos(C) -
j * sin(A) * sin(B) * sin(C) + k * cos(A) * sin(B) * sin(C) -
i * cos(B) * sin(C);
}

float calculateZ(int i, int j, int k) {
return k * cos(A) * cos(B) - j * sin(A) * cos(B) + i * sin(B);
}
تابع calculateForSurface در این برنامه وظیفه محاسبه مختصات دو‌بعدی یک نقطه از سطح مکعب را بر عهده دارد. این تابع مختصات سه‌بعدی یک نقطه از سطح مکعب را دریافت می‌کند، آن‌ها را با توجه به چرخش‌های مکعب به مختصات دو‌بعدی تبدیل می‌کند و سپس آن نقطه را روی صفحه‌نمایش دو‌بعدی به‌صورت کاراکتر ASCII نمایش می‌دهد.

ورودی‌های تابع:
- cubeX: مختصه‌ی ( x ) در فضای سه‌بعدی.
- cubeY: مختصه‌ی ( y ) در فضای سه‌بعدی.
- cubeZ: مختصه‌ی ( z ) در فضای سه‌بعدی.
- ch: کاراکتر ASCII که قرار است در محل محاسبه‌شده نمایش داده شود.

مراحل عملکرد:
1. محاسبه‌ی مختصات سه‌بعدی جدید:
- تابع ابتدا با استفاده از توابع calculateX، calculateY و calculateZ، مختصات سه‌بعدی جدید نقطه را بعد از اعمال چرخش‌ها (با توجه به زوایای ( A )، ( B )، و ( C )) محاسبه می‌کند. این توابع، مختصات نقطه را پس از چرخش حول محورهای مختلف به دست می‌آورند.

2. محاسبه‌ی مختصات عمق (z):
- مقدار مختصه‌ی ( z ) بعد از چرخش به فاصله‌ای از دوربین اضافه می‌شود تا نقطه از دید دوربین نمایش داده شود:

   z = calculateZ(cubeX, cubeY, cubeZ) + distanceFromCam;


3. محاسبه‌ی "معکوس فاصله از دوربین" (ooz):
- مقدار ooz برابر معکوس مقدار ( z ) است که برای محاسبه‌ی مقیاس‌بندی استفاده می‌شود:

   ooz = 1 / z;


4. تبدیل مختصات سه‌بعدی به دو‌بعدی:
- با استفاده از مقدارهای محاسبه‌شده، تابع مختصات دو‌بعدی (مختصات x و y برای نمایش در صفحه‌نمایش) را محاسبه می‌کند. این مقادیر به عنوان مختصات پیکسلی نقطه در صفحه‌ی دو‌بعدی استفاده می‌شوند:

   xp = (int)(width / 2 + horizontalOffset + K1 * ooz * x * 2);
yp = (int)(height / 2 + K1 * ooz * y);

این فرمول‌ها با استفاده از K1 (یک ثابت برای مقیاس‌بندی)، مختصات سه‌بعدی ( x ) و ( y ) را به مختصات دو‌بعدی صفحه‌نمایش تبدیل می‌کنند.

5. رسم نقطه روی صفحه‌نمایش:
- پس از محاسبه‌ی مختصات ( xp ) و ( yp ) (مختصات دو‌بعدی در صفحه‌نمایش)، تابع بررسی می‌کند که آیا این نقطه در محدوده‌ی نمایش (درون مرزهای صفحه‌نمایش) قرار دارد یا خیر. اگر نقطه در محدوده باشد و مقدار ooz بزرگ‌تر از مقدار موجود در بافر عمق (zBuffer) باشد (یعنی نقطه نزدیک‌تر به دوربین باشد):

   if (idx >= 0 && idx < width * height) {
if (ooz > zBuffer[idx]) {
zBuffer[idx] = ooz;
buffer[idx] = ch;
}
}

در این حالت، مقدار ooz در بافر عمق ذخیره می‌شود و کاراکتر ASCII مربوطه (ch) در موقعیت محاسبه‌شده‌ی آن قرار می‌گیرد.

نتیجه کلی:
تابع calculateForSurface یک نقطه از سطح مکعب را بعد از اعمال چرخش‌ها و تغییرات سه‌بعدی به مختصات دو‌بعدی تبدیل کرده و آن را با کاراکتر ASCII مربوطه روی صفحه‌ی کنسول رسم می‌کند. همچنین از طریق مقایسه‌ی عمق نقطه (مقدار z)، تضمین می‌کند که سطح‌های نزدیک‌تر به دوربین بر روی سطح‌های دورتر نمایش داده شوند.

void calculateForSurface(float cubeX, float cubeY, float cubeZ, int ch) {
x = calculateX(cubeX, cubeY, cubeZ);
y = calculateY(cubeX, cubeY, cubeZ);
z = calculateZ(cubeX, cubeY, cubeZ) + distanceFromCam;

ooz = 1 / z;

xp = (int)(width / 2 + horizontalOffset + K1 * ooz * x * 2);
yp = (int)(height / 2 + K1 * ooz * y);

idx = xp + yp * width;
if (idx >= 0 && idx < width * height) {
if (ooz > zBuffer[idx]) {
zBuffer[idx] = ooz;
buffer[idx] = ch;
}
}
}
در این بخش از کد، از تابع memset استفاده می‌شود تا آرایه‌های buffer و zBuffer با مقادیر مشخصی مقداردهی اولیه شوند. به طور دقیق‌تر:

خط اول:
memset(buffer, backgroundASCIICode, width * height);

buffer: این آرایه‌ای است که کاراکترهای ASCII را برای نمایش مکعب در صفحه‌نمایش ذخیره می‌کند.
backgroundASCIICode: یک کد ASCII که نمایانگر کاراکتر پس‌زمینه (فضای خالی) است. این مقدار برابر با ' ' (فضای خالی) است.
width * height: تعداد کل خانه‌های موجود در آرایه buffer است که معادل با تعداد کل کاراکترهایی است که روی صفحه‌نمایش کنسول نشان داده می‌شوند.

این خط از کد کل بافر buffer را با کاراکتر پس‌زمینه پر می‌کند (مثلاً فضای خالی یا یک کاراکتر دیگر). این کار هر فریم قبل از اینکه مکعب دوباره رسم شود، انجام می‌شود تا صفحه‌نمایش تمیز شود.
خط دوم:
memset(zBuffer, 0, width * height * 4);

zBuffer: آرایه‌ای که عمق هر نقطه (مقدار \( z \)) را در خود نگه می‌دارد. از این آرایه برای اطمینان از این استفاده می‌شود که نقاط نزدیک‌تر به دوربین روی نقاط دورتر نمایش داده شوند.
- `0`: تمام خانه‌های این آرایه با صفر مقداردهی می‌شوند. این یعنی در ابتدا هیچ نقطه‌ای از مکعب در صفحه رسم نشده است.
width * height * 4`: اندازه‌ی کل آرایه zBuffer که برابر با تعداد کل پیکسل‌های صفحه ضرب‌در اندازه‌ی هر عنصر (۴ بایت برای هر مقدار عمق) است.

این خط از کد آرایه‌ی zBuffer را قبل از هر فریم با صفر مقداردهی می‌کند تا در هر فریم جدید مقادیر عمق نقاط از نو محاسبه شوند.
هدف کلی:
این دو خط کد در ابتدای هر فریم:
1. buffer را با کاراکتر پس‌زمینه (معمولاً فضای خالی) پر می‌کند تا صفحه‌نمایش تمیز شود.
2. zBuffer را صفر می‌کند تا بتواند عمق نقاط جدید را بدون تأثیر از مقادیر قبلی محاسبه کند.

این عملیات‌ها برای آماده‌سازی هر فریم جدید ضروری هستند تا انیمیشن چرخش مکعب به درستی اجرا شود.
CD
در این بخش از کد، از تابع memset استفاده می‌شود تا آرایه‌های buffer و zBuffer با مقادیر مشخصی مقداردهی اولیه شوند. به طور دقیق‌تر: خط اول: memset(buffer, backgroundASCIICode, width * height); buffer: این آرایه‌ای است که کاراکترهای ASCII را برای نمایش مکعب…
این بخش از کد وظیفه دارد سطح‌های مختلف مکعب را محاسبه و برای هر سطح آن‌ها را با استفاده از کاراکترهای ASCII مختلف روی صفحه نمایش دهد. این کار با استفاده از دو حلقه‌ی تو در تو انجام می‌شود که نقاط مختلف روی سطوح مکعب را بررسی می‌کنند و برای هر نقطه، تابع calculateForSurface فراخوانی می‌شود تا مختصات دو‌بعدی آن نقطه روی صفحه‌نمایش محاسبه و نمایش داده شود.

توضیحات کلی:
- حلقه‌های تو در تو: این حلقه‌ها کل سطح مکعب را نقطه به نقطه بررسی می‌کنند و برای هر نقطه مختصات سه‌بعدی آن را به مختصات دو‌بعدی تبدیل می‌کنند.
- کاراکترهای مختلف برای سطوح مکعب: هر سطح مکعب با یک کاراکتر ASCII متفاوت رسم می‌شود تا بتوانید هر سطح را به وضوح از دیگر سطوح تشخیص دهید.
- تابع calculateForSurface: این تابع مختصات دو‌بعدی نقطه‌ای از سطح مکعب را محاسبه می‌کند و در صورت برآورده شدن شرایط (مثل عمق)، آن را روی صفحه‌نمایش قرار می‌دهد.
جزئیات هر خط:

1. حلقه‌های تو در تو:
   for (float cubeX = -cubeWidth; cubeX < cubeWidth; cubeX += incrementSpeed) {
for (float cubeY = -cubeWidth; cubeY < cubeWidth; cubeY += incrementSpeed) {

- این دو حلقه مختصات نقاط مختلفی را روی سطوح مکعب بررسی می‌کنند.
- متغیر cubeX و cubeY به ترتیب نقاطی را در طول محورهای ( x ) و ( y ) روی سطح‌های مختلف مکعب تکرار می‌کنند.
- حلقه‌ها از مقدار -cubeWidth تا +cubeWidth برای هر دو متغیر تکرار می‌شوند. این یعنی کل سطح مکعب با در نظر گرفتن cubeWidth به عنوان اندازه هر ضلع پیمایش می‌شود.
- incrementSpeed: میزان تغییرات ( x ) و ( y ) در هر تکرار، که تعیین می‌کند نقاط با چه فاصله‌ای از هم بررسی شوند.

2. فراخوانی calculateForSurface برای هر سطح مکعب:
این تابع برای هر سطح مکعب ۶ بار برای نقاط مختلف فراخوانی می‌شود تا همه‌ی شش سطح مکعب رسم شوند:

- سطح 1 (جلو):
     calculateForSurface(cubeX, cubeY, -cubeWidth, '@'); // Side 1

این خط مسئول رسم سطح جلویی مکعب است که در مختصات ( z = -cubeWidth ) قرار دارد. هر نقطه روی این سطح با کاراکتر @ رسم می‌شود.

- سطح 2 (راست):
     calculateForSurface(cubeWidth, cubeY, cubeX, '$'); // Side 2

این خط مسئول رسم سطح سمت راست مکعب است که در مختصات ( x = cubeWidth ) قرار دارد. این سطح با کاراکتر $ نمایش داده می‌شود.

- سطح 3 (چپ):
     calculateForSurface(-cubeWidth, cubeY, -cubeX, '~'); // Side 3

این خط مسئول رسم سطح سمت چپ مکعب است که در مختصات ( x = -cubeWidth ) قرار دارد و با کاراکتر ~ نمایش داده می‌شود.

- سطح 4 (پشت):
     calculateForSurface(-cubeX, cubeY, cubeWidth, '#');  // Side 4

این خط مسئول رسم سطح پشت مکعب است که در مختصات ( z = cubeWidth ) قرار دارد و با کاراکتر # نمایش داده می‌شود.

- سطح 5 (بالا):
     calculateForSurface(cubeX, -cubeWidth, -cubeY, ';'); // Side 5

این خط مسئول رسم سطح بالای مکعب است که در مختصات ( y = -cubeWidth ) قرار دارد و با کاراکتر ; نمایش داده می‌شود.

- سطح 6 (پایین):
     calculateForSurface(cubeX, cubeWidth, cubeY, '+'); // Side 6

این خط مسئول رسم سطح پایین مکعب است که در مختصات ( y = cubeWidth ) قرار دارد و با کاراکتر + نمایش داده می‌شود.



هدف:
این حلقه‌ها برای رندر کردن و نمایش هر شش سطح مکعب استفاده می‌شوند. با این کار مکعب سه‌بعدی به‌صورت چرخان در صفحه‌نمایش دو‌بعدی ASCII نمایش داده می‌شود.

for (float cubeX = -cubeWidth; cubeX < cubeWidth; cubeX += incrementSpeed) {
for (float cubeY = -cubeWidth; cubeY < cubeWidth; cubeY += incrementSpeed) {
calculateForSurface(cubeX, cubeY, -cubeWidth, '@'); // Side 1
calculateForSurface(cubeWidth, cubeY, cubeX, '$'); // Side 2
calculateForSurface(-cubeWidth, cubeY, -cubeX, '~'); // Side 3
calculateForSurface(-cubeX, cubeY, cubeWidth, '#'); // Side 4
calculateForSurface(cubeX, -cubeWidth, -cubeY, ';'); // Side 5
calculateForSurface(cubeX, cubeWidth, cubeY, '+'); // Side 6
}
}
x1b[H یک کد کنترلی ANSI است که نشان‌دهنده‌ی حرکت مکان‌نما (cursor) به بالای صفحه‌نمایش است.
x1b برابر با کاراکتر Escape (ESC) است.
[H دستور حرکت مکان‌نما به خانه‌ی اول (بالا سمت چپ) صفحه است.
این بخش کد باعث می‌شود مکان‌نما در هر فریم به موقعیت شروع صفحه بازگردد تا محتوای جدید به جای اینکه زیر محتوای قبلی چاپ شود، از بالا دوباره چاپ گردد. این کار برای ایجاد توهم انیمیشن ضروری است، زیرا هر فریم جدید به جای پاک کردن صفحه و دوباره چاپ کردن، کل صفحه را دوباره‌نویسی می‌کند.

printf("\x1b[H");
for (int k = 0; k < width * height; k++) {
putchar(k % width ? buffer[k] : 10);
}
و در آخر با این بخش از کد مقدار زوایایی چرخش مکعب را تغییر میدیم

A += 0.05;
B += 0.05;
C += 0.01;
usleep(8000 * 2);
CD
Video
کل کد در نهایت شد :

#include <math.h>
#include <stdio.h>
#include <string.h>
#include <windows.h>

void usleep(__int64 usec)
{
HANDLE timer;
LARGE_INTEGER ft;

ft.QuadPart = -(10 * usec); // Convert to 100 nanosecond interval, negative value indicates relative time

timer = CreateWaitableTimer(NULL, TRUE, NULL);
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
WaitForSingleObject(timer, INFINITE);
CloseHandle(timer);
}

float A, B, C;
float cubeWidth = 10;
int width = 160, height = 44;
float zBuffer[160 * 44];
char buffer[160 * 44];
int backgroundASCIICode = ' ';
int distanceFromCam = 100;
float horizontalOffset;
float K1 = 40;
float incrementSpeed = 0.5;
float x, y, z;
float ooz;
int xp, yp;
int idx;

float calculateX(int i, int j, int k) {
return j * sin(A) * sin(B) * cos(C) - k * cos(A) * sin(B) * cos(C) +
j * cos(A) * sin(C) + k * sin(A) * sin(C) + i * cos(B) * cos(C);
}

float calculateY(int i, int j, int k) {
return j * cos(A) * cos(C) + k * sin(A) * cos(C) -
j * sin(A) * sin(B) * sin(C) + k * cos(A) * sin(B) * sin(C) -
i * cos(B) * sin(C);
}

float calculateZ(int i, int j, int k) {
return k * cos(A) * cos(B) - j * sin(A) * cos(B) + i * sin(B);
}

void calculateForSurface(float cubeX, float cubeY, float cubeZ, int ch) {
x = calculateX(cubeX, cubeY, cubeZ);
y = calculateY(cubeX, cubeY, cubeZ);
z = calculateZ(cubeX, cubeY, cubeZ) + distanceFromCam;

ooz = 1 / z;

xp = (int)(width / 2 + horizontalOffset + K1 * ooz * x * 2);
yp = (int)(height / 2 + K1 * ooz * y);

idx = xp + yp * width;
if (idx >= 0 && idx < width * height) {
if (ooz > zBuffer[idx]) {
zBuffer[idx] = ooz;
buffer[idx] = ch;
}
}
}

int main() {
printf("\x1b[2J");
while (1) {
memset(buffer, backgroundASCIICode, width * height);
memset(zBuffer, 0, width * height * 4);
cubeWidth = 20;
horizontalOffset = -2 * cubeWidth;
for (float cubeX = -cubeWidth; cubeX < cubeWidth; cubeX += incrementSpeed) {
for (float cubeY = -cubeWidth; cubeY < cubeWidth; cubeY += incrementSpeed) {
calculateForSurface(cubeX, cubeY, -cubeWidth, '@'); // Side 1
calculateForSurface(cubeWidth, cubeY, cubeX, '$'); // Side 2
calculateForSurface(-cubeWidth, cubeY, -cubeX, '~'); // Side 3
calculateForSurface(-cubeX, cubeY, cubeWidth, '#'); // Side 4
calculateForSurface(cubeX, -cubeWidth, -cubeY, ';'); // Side 5
calculateForSurface(cubeX, cubeWidth, cubeY, '+'); // Side 6
}
}
cubeWidth = 10;
horizontalOffset = 1 * cubeWidth;
printf("\x1b[H");
for (int k = 0; k < width * height; k++) {
putchar(k % width ? buffer[k] : 10);
}

A += 0.05;
B += 0.05;
C += 0.01;
usleep(8000 * 2);
}
return 0;
}


💿Records 3
🗃️CD Archive
تاحالا برات پیش اومده؟

یه ایده ناب به ذهنت میزنه، با هیجان شروع میکنی به کد زدن... کلی تغییر میدی، اما بعد میبینی مسیر اشتباه بوده! حالا میخوای برگردی به حالت قبلی، اما انقدر تغییرات زیاده که حتی "Ctrl + Z" هم جوابگو نیست!

اینجاست که Git مثل یک قهرمان وارد میشه! 🦸‍♂️

🔍 Git چیه؟
یک سیستم کنترل نسخه هوشمند که:
تمام تغییرات کدتو به ترتیب ذخیره میکنه (مثل یه ماشین زمان!)
اجازه میده به هر نسخه قبلی برگردی (حتی اگر ماهها پیش باشه)
همکاری رو آسان میکنه (بدون ترس از گم شدن کدها!)

📌 چطوری کار میکنه؟
1️⃣ Commit (ثبت تغییرات):
- هر وقت به یه تغییر رضایت دادی، Git یه عکس فوری از کدت میگیره و اون رو با یه پیام ذخیره میکنه

2️⃣ Branch (شاخه‌بندی):
- میتونی نسخه‌های موازی از پروژه بسازی (مثلاً برای تست یه فیچر جدید بدون خراب کردن نسخه اصلی)

3️⃣ Merge (ادغام کردن):
- اگر تغییراتت اوکی بود، میتونی اونها رو به نسخه اصلی اضافه کنی

💡 چرا Git ضروریه؟
- دیگه هیچوقت کدتو گم نمیکنی!
- میتونی جرأت کنی و آزمایش‌های جدید انجام بدی (اگر اشتباه شد، برمیگردی عقب!)
- توی کار تیمی، همه باهم بدون دردسر کد مینویسن!

🚀 توضیحات بیشتر تو کامنتا!
اگه دوست داری یاد بگیری چطوری از Git استفاده کنی، تو کامنت های این پست قراره به مرور آموزش رایگان Git رو شروع کنیم...

💿Records 4
🗃️CD Archive
This media is not supported in your browser
VIEW IN TELEGRAM
Floppies are like Jesus
They died to become the Icon of Saving ;D
#meme
👏7
دوران سختی بود🖤

گذشت

ادامه میدیم تا دورهم چیز های جدیدتر یادبگیریم🌱

فعالیت اینجا رو هم بیشتر میکنیم
دور هم مطالب جدید یادبگیریم
ایده های جدید
اهنگ گوش بدیم و…

به امید روزهای بهتر 🦔
Forwarded from جJج
Way Down We Go
Kaleo