├── .gitignore ├── src ├── canvas.h ├── utils.h ├── buffer.h ├── utils.c ├── matrix.h ├── graphics.h ├── buffer.c ├── matrix.c ├── canvas.c ├── graphics.c └── main.c ├── .vscode ├── tasks.json └── launch.json ├── README.md ├── Makefile └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | build -------------------------------------------------------------------------------- /src/canvas.h: -------------------------------------------------------------------------------- 1 | #ifndef CANVAS_H 2 | #define CANVAS_H 3 | 4 | #include "matrix.h" 5 | 6 | void draw_point(int x, int y, int intensity); 7 | void draw_triangle(const V2 *v1, const V2 *v2, const V2 *v3, int intensity); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #define MIN(a,b) (((a)<(b))?(a):(b)) 5 | #define MAX(a,b) (((a)>(b))?(a):(b)) 6 | 7 | void swap_vertices(V2 *v1, V2 *v2); 8 | int cross_product_2d(const V2 *v1, const V2 *v2); 9 | #endif 10 | -------------------------------------------------------------------------------- /src/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H 2 | #define BUFFER_H 3 | 4 | #include 5 | #include 6 | 7 | #define STR_SIZE 30 8 | 9 | char *init_buffer(); 10 | void flush_buffer(); 11 | void add_to_buffer(const char *str, const int x, const int y); 12 | 13 | #endif // BUFFER_H 14 | -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | #include "canvas.h" 2 | 3 | void swap_vertices(V2 *v1, V2 *v2) 4 | { 5 | int tempx = v1->x; 6 | int tempy = v1->y; 7 | 8 | v1->x = v2->x; 9 | v1->y = v2->y; 10 | 11 | v2->x = tempx; 12 | v2->y = tempy; 13 | } 14 | 15 | int cross_product_2d(const V2 *v1, const V2 *v2) 16 | { 17 | return v1->x * v2->y - v1->y * v2->x; 18 | } 19 | 20 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "shell", 6 | "label": "Build with make", 7 | "command": "/usr/bin/make", 8 | "args": [], 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "group": "build", 13 | "icon": { "color": "terminal.ansiCyan", "id": "tools" } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Rotating 3d cube](https://github.com/hamza512b/ccube/assets/63897375/f3909e9e-fdd1-456f-876a-0dc338590b6d) 2 | 3 | # Rotating Cube in terminal 4 | 5 | I have made this rotating cube from scratch, too learn more about C. I have always been fascinated by 3d computer graphics, which I usually do in WebGL and JavaScript. But since I am learning C, I tried to do this. 6 | 7 | # To run yourself 8 | 9 | Make sure you have make and some c compiler and simple run: 10 | 11 | ``` 12 | make 13 | ``` 14 | 15 | and then run executable with the width of characters as argument. 16 | 17 | ``` 18 | ./build/draw_cube 40 19 | ``` 20 | 21 | > Note: If you input a large value and the cube does not fit the screen, make sure to resize your terminal. 22 | -------------------------------------------------------------------------------- /src/matrix.h: -------------------------------------------------------------------------------- 1 | #ifndef MATRIX_H 2 | #define MATRIX_H 3 | #define M_PI 3.14159265358979323846 4 | 5 | typedef struct 6 | { 7 | int x; 8 | int y; 9 | } V2; 10 | 11 | typedef struct 12 | { 13 | double x; 14 | double y; 15 | double z; 16 | } V3; 17 | 18 | typedef struct 19 | { 20 | double m[4][4]; 21 | } M44; 22 | 23 | // Multiply a vertex by a matrix 24 | V3 multiply_vertex_matrix(const V3 *v, const M44 *m); 25 | 26 | // Multiply two matrices 27 | M44 multiply_matrices(const M44 *m1, const M44 *m2); 28 | 29 | // Identity matrix 30 | M44 identity_matrix(); 31 | 32 | // Function to compute the cross product of two vectors 33 | V3 cross_product(V3 *v1, V3 *v2); 34 | 35 | // Function to compute the dot product of two vectors 36 | double dot_product(V3 *v1, V3 *v2); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Define the compiler 2 | CC = gcc 3 | 4 | # Define compiler flags 5 | CFLAGS = -Wall -lm 6 | 7 | # Define the source and build directories 8 | SRC_DIR = src 9 | BUILD_DIR = build 10 | 11 | # Define the target executable 12 | TARGET = $(BUILD_DIR)/draw_cube 13 | 14 | # List all source files 15 | SRC_FILES = $(wildcard $(SRC_DIR)/*.c) 16 | 17 | # Generate object files list 18 | OBJ_FILES = $(patsubst $(SRC_DIR)/%.c, $(BUILD_DIR)/%.o, $(SRC_FILES)) 19 | 20 | # Default target 21 | all: $(TARGET) 22 | 23 | # Rule to build the target executable 24 | $(TARGET): $(OBJ_FILES) 25 | $(CC) $(CFLAGS) -o $@ $^ -lm 26 | 27 | # Rule to build object files 28 | $(BUILD_DIR)/%.o: $(SRC_DIR)/%.c 29 | @mkdir -p $(BUILD_DIR) 30 | $(CC) $(CFLAGS) -c $< -o $@ 31 | 32 | # Clean up build directory 33 | clean: 34 | rm -rf $(BUILD_DIR) 35 | 36 | # Phony targets 37 | .PHONY: all clean 38 | 39 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug file ./build/draw_cube", 5 | "type": "cppdbg", 6 | "request": "launch", 7 | "program": "${workspaceFolder}/build/draw_cube", 8 | "args": ["50", "50"], 9 | "stopAtEntry": false, 10 | "cwd": "${workspaceFolder}", 11 | "environment": [], 12 | "externalConsole": false, 13 | "MIMode": "gdb", 14 | "setupCommands": [ 15 | { 16 | "description": "Enable pretty-printing for gdb", 17 | "text": "-enable-pretty-printing", 18 | "ignoreFailures": true 19 | }, 20 | { 21 | "description": "Set Disassembly Flavor to Intel", 22 | "text": "-gdb-set disassembly-flavor intel", 23 | "ignoreFailures": true 24 | } 25 | ], 26 | "preLaunchTask": "Build with make" 27 | } 28 | ], 29 | "version": "2.0.0" 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Hamza 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/graphics.h: -------------------------------------------------------------------------------- 1 | #ifndef GRAPHICS_H 2 | #define GRAPHICS_H 3 | 4 | #include "matrix.h" 5 | 6 | typedef struct 7 | { 8 | V3 vertices[36]; 9 | int faceColors[6]; 10 | } Cube; 11 | 12 | // Global variable for the cube (clockwise order so culling works) 13 | Cube cube = { 14 | .vertices = { 15 | // Front face 16 | {-1, -1, -1}, {1, -1, -1}, {1, 1, -1}, 17 | {-1, -1, -1}, {1, 1, -1}, {-1, 1, -1}, 18 | 19 | 20 | 21 | // Back face 22 | {1, -1, 1}, {-1, -1, 1}, {-1, 1, 1}, 23 | {1, -1, 1}, {-1, 1, 1}, {1, 1, 1}, 24 | 25 | // Left face 26 | {-1, -1, 1}, {-1, -1, -1}, {-1, 1, -1}, 27 | {-1, -1, 1}, {-1, 1, -1}, {-1, 1, 1}, 28 | 29 | // Right face 30 | {1, -1, -1}, {1, -1, 1}, {1, 1, 1}, 31 | {1, -1, -1}, {1, 1, 1}, {1, 1, -1}, 32 | 33 | // Top face 34 | {-1, 1, -1}, {1, 1, -1}, {1, 1, 1}, 35 | {-1, 1, -1}, {1, 1, 1}, {-1, 1, 1}, 36 | 37 | // Bottom face 38 | {1, -1, -1}, {-1, -1, -1}, {-1, -1, 1}, 39 | {1, -1, -1}, {-1, -1, 1}, {1, -1, 1}, 40 | }, 41 | }; 42 | M44 rotation_matrix_x(double angle); 43 | 44 | M44 rotation_matrix_y(double angle); 45 | 46 | M44 rotation_matrix_z(double angle); 47 | 48 | M44 translation_matrix(double tx, double ty, double tz); 49 | 50 | M44 scaling_matrix(double sx, double sy, double sz); 51 | 52 | M44 projection_matrix(double fov, double aspect, double near, double far); 53 | 54 | int is_back_face(V3 *v0, V3 *v1, V3 *v2); 55 | #endif 56 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include "buffer.h" 2 | #include 3 | 4 | const char *CLER_ESCAPE_STR = "\033[H\033[J"; 5 | 6 | extern int canvas_width; 7 | extern int canvas_height; 8 | extern char *buffer; 9 | 10 | char *init_buffer() 11 | { 12 | char *buffer = malloc(canvas_height * (canvas_width + 1) * STR_SIZE); 13 | return buffer; 14 | } 15 | 16 | void flush_buffer() 17 | { 18 | // FLush 19 | fwrite(CLER_ESCAPE_STR, sizeof(char), strlen(CLER_ESCAPE_STR), stdout); 20 | char *str = buffer; 21 | for (int i = 0; i <= STR_SIZE * canvas_height * (canvas_width + 1); str++, i++) 22 | { 23 | // If it is end line chunk 24 | 25 | if (*str != '\0') 26 | { 27 | fputc(*str, stdout); 28 | continue; 29 | } 30 | } 31 | fflush(stdout); 32 | 33 | // Reset 34 | str = buffer; 35 | for (int i = 0; i <= STR_SIZE * canvas_height * (canvas_width + 1); str++, i++) 36 | { 37 | if (i % (STR_SIZE * (canvas_width + 1)) == 0) 38 | { 39 | 40 | *str = '\n'; 41 | continue; 42 | } 43 | if (i % STR_SIZE == 0) 44 | { 45 | 46 | *str = ' '; 47 | continue; 48 | } 49 | *str = '\0'; 50 | } 51 | } 52 | 53 | void add_to_buffer(const char *str, const int x, const int y) 54 | { 55 | char *buffer_ptr = buffer + (y * (canvas_width + 1) * STR_SIZE + x * STR_SIZE); 56 | int i = 0; 57 | for (; i < STR_SIZE && str[i] != '\0'; i++) 58 | { 59 | *buffer_ptr = str[i]; 60 | buffer_ptr++; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/matrix.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "matrix.h" 3 | 4 | // Multiply a vertex by a matrix 5 | V3 multiply_vertex_matrix(const V3 *v, const M44 *m) 6 | { 7 | V3 result = {0}; 8 | result.x = v->x * m->m[0][0] + v->y * m->m[0][1] + v->z * m->m[0][2] + m->m[0][3]; 9 | result.y = v->x * m->m[1][0] + v->y * m->m[1][1] + v->z * m->m[1][2] + m->m[1][3]; 10 | result.z = v->x * m->m[2][0] + v->y * m->m[2][1] + v->z * m->m[2][2] + m->m[2][3]; 11 | 12 | double w = v->x * m->m[3][0] + v->y * m->m[3][1] + v->z * m->m[3][2] + m->m[3][3]; 13 | if (w != 0) 14 | { 15 | result.x /= w; 16 | result.y /= w; 17 | result.z /= w; 18 | } 19 | return result; 20 | } 21 | 22 | // Multiply two matrices 23 | M44 multiply_matrices(const M44 *m1, const M44 *m2) 24 | { 25 | M44 result = {0}; 26 | for (int i = 0; i < 4; ++i) 27 | { 28 | for (int j = 0; j < 4; ++j) 29 | { 30 | for (int k = 0; k < 4; ++k) 31 | result.m[i][j] += m1->m[i][k] * m2->m[k][j]; 32 | } 33 | } 34 | return result; 35 | } 36 | 37 | // Identity matrix 38 | M44 identity_matrix() 39 | { 40 | M44 m = {0}; 41 | for (int i = 0; i < 4; ++i) 42 | { 43 | m.m[i][i] = 1.0f; 44 | } 45 | return m; 46 | } 47 | 48 | // Function to compute the cross product of two vectors 49 | V3 cross_product(V3 *v1, V3 *v2) 50 | { 51 | V3 result = {0}; 52 | result.x = v1->y * v2->z - v1->z * v2->y; 53 | result.y = v1->z * v2->x - v1->x * v2->z; 54 | result.z = v1->x * v2->y - v1->y * v2->x; 55 | return result; 56 | } 57 | 58 | // Function to compute the dot product of two vectors 59 | double dot_product(V3 *v1, V3 *v2) 60 | { 61 | return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; 62 | } 63 | -------------------------------------------------------------------------------- /src/canvas.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "canvas.h" 3 | #include "utils.h" 4 | #include "buffer.h" 5 | 6 | void draw_point(int x, int y, int intensity) 7 | { 8 | extern int canvas_width; 9 | extern int canvas_height; 10 | 11 | if (x > canvas_width || x < 0) 12 | return; 13 | 14 | if (y > canvas_height || y < 0) 15 | return; 16 | 17 | static const char *colors[] = { 18 | "\033[38;5;236m", 19 | "\033[38;5;240m", 20 | "\033[38;5;242m", 21 | "\033[38;5;247m", 22 | "\033[38;5;251m", 23 | "\033[38;5;255m", 24 | }; 25 | 26 | if (intensity < 0) 27 | intensity = 0; 28 | if (intensity > 5) 29 | intensity = 5; 30 | 31 | // Move cursor and set color 32 | char command[STR_SIZE]; 33 | snprintf(command, sizeof(command), "%s#\033[0m", colors[intensity]); 34 | add_to_buffer(command, x, y); 35 | } 36 | 37 | void draw_triangle(const V2 *v1, const V2 *v2, const V2 *v3, int intensity) 38 | { 39 | /* get the bounding box of the triangle */ 40 | int maxX = MAX(v1->x, MAX(v2->x, v3->x)); 41 | int minX = MIN(v1->x, MIN(v2->x, v3->x)); 42 | int maxY = MAX(v1->y, MAX(v2->y, v3->y)); 43 | int minY = MIN(v1->y, MIN(v2->y, v3->y)); 44 | 45 | V2 vs1 = {v2->x - v1->x, v2->y - v1->y}; 46 | V2 vs2 = {v3->x - v1->x, v3->y - v1->y}; 47 | 48 | for (int x = minX; x <= maxX; x++) 49 | { 50 | for (int y = minY; y <= maxY; y++) 51 | { 52 | V2 q = {x - v1->x, y - v1->y}; 53 | 54 | double s = (double)cross_product_2d(&q, &vs2) / cross_product_2d(&vs1, &vs2); 55 | double t = (double)cross_product_2d(&vs1, &q) / cross_product_2d(&vs1, &vs2); 56 | 57 | if ((s >= 0) && (t >= 0) && (s + t <= 1)) 58 | { /* inside triangle */ 59 | draw_point(x, y, intensity); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/graphics.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "matrix.h" 3 | #include "canvas.h" 4 | 5 | M44 rotation_matrix_x(double angle) 6 | { 7 | M44 m = identity_matrix(); 8 | m.m[1][1] = cos(angle); 9 | m.m[1][2] = -sin(angle); 10 | m.m[2][1] = sin(angle); 11 | m.m[2][2] = cos(angle); 12 | return m; 13 | } 14 | 15 | M44 rotation_matrix_y(double angle) 16 | { 17 | M44 m = identity_matrix(); 18 | m.m[0][0] = cos(angle); 19 | m.m[0][2] = sin(angle); 20 | m.m[2][0] = -sin(angle); 21 | m.m[2][2] = cos(angle); 22 | return m; 23 | } 24 | 25 | M44 rotation_matrix_z(double angle) 26 | { 27 | M44 m = identity_matrix(); 28 | m.m[0][0] = cos(angle); 29 | m.m[0][1] = -sin(angle); 30 | m.m[1][0] = sin(angle); 31 | m.m[1][1] = cos(angle); 32 | return m; 33 | } 34 | 35 | M44 translation_matrix(double tx, double ty, double tz) 36 | { 37 | M44 m = identity_matrix(); 38 | m.m[0][3] = tx; 39 | m.m[1][3] = ty; 40 | m.m[2][3] = tz; 41 | return m; 42 | } 43 | 44 | M44 scaling_matrix(double sx, double sy, double sz) 45 | { 46 | M44 m = identity_matrix(); 47 | m.m[0][0] = sx; 48 | m.m[1][1] = sy; 49 | m.m[2][2] = sz; 50 | return m; 51 | } 52 | 53 | M44 projection_matrix(double fov, double aspect, double near, double far) 54 | { 55 | M44 m = {0}; 56 | double f = 1.0 / tan(fov / 2.0); 57 | m.m[0][0] = f / aspect; 58 | m.m[1][1] = f; 59 | m.m[2][2] = (far + near) / (near - far); 60 | m.m[2][3] = (2 * far * near) / (near - far); 61 | m.m[3][2] = -1; 62 | return m; 63 | } 64 | 65 | // Compute back-face culling 66 | int is_back_face(const V3 *v1, const V3 *v2, const V3 *v3) 67 | { 68 | V3 v1v2 = {v2->x - v1->x, v2->y - v1->y, v2->z - v1->z}; 69 | V3 v1v3 = {v3->x - v1->x, v3->y - v1->y, v3->z - v1->z}; 70 | V3 normal = cross_product(&v1v2, &v1v3); 71 | V3 view = {0, 0, 1}; 72 | return dot_product(&normal, &view) < 0; 73 | } -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "canvas.h" 7 | #include "utils.h" 8 | #include "graphics.h" 9 | #include "matrix.h" 10 | #include "buffer.h" 11 | 12 | int canvas_height, canvas_width; 13 | char *buffer = NULL; 14 | 15 | int main(int argc, char *argv[]) 16 | { 17 | if (argc == 2) 18 | { 19 | canvas_height = atoi(argv[1]); 20 | canvas_width = canvas_height; 21 | } 22 | 23 | if (argc == 3) 24 | { 25 | canvas_height = atoi(argv[1]); 26 | canvas_width = atoi(argv[2]); 27 | } 28 | 29 | if (argc != 2 && argc != 3) 30 | { 31 | printf("Usage: %s \n", argv[0]); 32 | return 1; 33 | } 34 | 35 | buffer = init_buffer(); 36 | 37 | srand(time(NULL)); // Seed the random number generator with the current time 38 | 39 | double i = 0; 40 | double j = M_PI / 2; 41 | while (1) 42 | { 43 | // Define transformation parameters 44 | double angle_x = (i += 0.075); 45 | double angle_y = (j += 0.05); 46 | double angle_z = 0; 47 | double tx = 0, ty = 2.2, tz = 10; 48 | double sx = 1, sy = 1, sz = 1; 49 | double fov = M_PI / 4; // 45 degrees 50 | double aspect = 0.5; // 1:2 aspect ratio 51 | double near = 0.1, far = 100.0; 52 | 53 | // Combine the transformation matrices 54 | M44 transformation = identity_matrix(); 55 | 56 | M44 projection = projection_matrix(fov, aspect, near, far); 57 | transformation = multiply_matrices(&transformation, &projection); 58 | 59 | M44 translation = translation_matrix(tx, ty, tz); 60 | transformation = multiply_matrices(&transformation, &translation); 61 | 62 | M44 rotation_z = rotation_matrix_z(angle_z); 63 | transformation = multiply_matrices(&transformation, &rotation_z); 64 | 65 | M44 rotation_y = rotation_matrix_y(angle_y); 66 | transformation = multiply_matrices(&transformation, &rotation_y); 67 | 68 | M44 rotation_x = rotation_matrix_x(angle_x); 69 | transformation = multiply_matrices(&transformation, &rotation_x); 70 | 71 | M44 scaling = scaling_matrix(sx, sy, sz); 72 | transformation = multiply_matrices(&transformation, &scaling); 73 | 74 | // Clear screen and draw the transformed and projected cube 75 | flush_buffer(); 76 | 77 | // Draw the cube 78 | for (int k = 0; k < 36; k += 3) 79 | { 80 | V3 v1 = cube.vertices[k]; 81 | V3 v2 = cube.vertices[k + 1]; 82 | V3 v3 = cube.vertices[k + 2]; 83 | 84 | // Transform vertices 85 | V3 v1_transformed = multiply_vertex_matrix(&v1, &transformation); 86 | V3 v2_transformed = multiply_vertex_matrix(&v2, &transformation); 87 | V3 v3_transformed = multiply_vertex_matrix(&v3, &transformation); 88 | 89 | // Back-face culling 90 | if (is_back_face(&v1_transformed, &v2_transformed, &v3_transformed)) 91 | continue; 92 | 93 | // Project vertices 94 | V2 v1_projected = {(v1_transformed.x / v1_transformed.z + 1) * canvas_width / 2, 95 | (v1_transformed.y / v1_transformed.z + 1) * canvas_height / 2}; 96 | V2 v2_projected = {(v2_transformed.x / v2_transformed.z + 1) * canvas_width / 2, 97 | (v2_transformed.y / v2_transformed.z + 1) * canvas_height / 2}; 98 | V2 v3_projected = {(v3_transformed.x / v3_transformed.z + 1) * canvas_width / 2, 99 | (v3_transformed.y / v3_transformed.z + 1) * canvas_height / 2}; 100 | 101 | // Draw the triangle 102 | int col = (int)(k / 6); 103 | draw_triangle(&v1_projected, &v2_projected, &v3_projected, col); 104 | } 105 | 106 | flush_buffer(); 107 | usleep(500000); // sleep for 500 milliseconds 108 | } 109 | printf("\n"); 110 | free(buffer); 111 | return 0; 112 | } 113 | --------------------------------------------------------------------------------