@pixel-normal-edit/mcp 2.0.7 → 2.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/domains/art-tree/advanced_lessons.js +11 -11
- package/domains/art-tree/advanced_tools.js +12 -12
- package/domains/art-tree/analysis_lessons.js +32 -0
- package/domains/art-tree/analysis_shapes.js +146 -0
- package/domains/art-tree/analysis_tools.js +49 -0
- package/domains/art-tree/hidden_lessons.js +3 -3
- package/domains/art-tree/index.js +10 -0
- package/domains/art-tree/layer_lessons.js +32 -0
- package/domains/art-tree/layer_shapes.js +117 -0
- package/domains/art-tree/layer_tools.js +43 -0
- package/domains/art-tree/lessons.js +19 -19
- package/domains/art-tree/light_lessons.js +3 -3
- package/domains/art-tree/light_shapes.js +5 -5
- package/domains/art-tree/material_lessons.js +71 -0
- package/domains/art-tree/material_shapes.js +130 -0
- package/domains/art-tree/material_tools.js +49 -0
- package/domains/art-tree/perspective_lessons.js +1 -1
- package/domains/art-tree/perspective_tools.js +1 -1
- package/domains/art-tree/sky_lessons_1.js +82 -0
- package/domains/art-tree/sky_shapes_1.js +68 -0
- package/domains/art-tree/sky_tools.js +51 -0
- package/domains/art-tree/surface_lessons.js +7 -7
- package/domains/art-tree/tools.js +27 -27
- package/domains/art-tree/transform_lessons_1.js +2 -2
- package/domains/art-tree/transform_lessons_2.js +4 -4
- package/domains/art-tree/transform_tools.js +8 -8
- package/domains/art-tree/vocab_lessons.js +32 -0
- package/domains/art-tree/vocab_shapes.js +116 -0
- package/domains/art-tree/vocab_tools.js +41 -0
- package/package.json +1 -1
- package/tools/index.js +2 -0
- package/tools/layer.js +80 -0
|
@@ -166,7 +166,7 @@ function lessonComposition(canvasSize = 32) {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
/**
|
|
169
|
-
* Lesson 8: Freehand —
|
|
169
|
+
* Lesson 8: Freehand — Practice freehand drawing with simulated hand tremor.
|
|
170
170
|
*/
|
|
171
171
|
function lessonFreehand(canvasSize = 32, color = '#795548') {
|
|
172
172
|
const margin = Math.floor(canvasSize * 0.1);
|
|
@@ -179,7 +179,7 @@ function lessonFreehand(canvasSize = 32, color = '#795548') {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
* Lesson 9: Proportions and Angles —
|
|
182
|
+
* Lesson 9: Proportions and Angles — Practice maintaining proportions and angles.
|
|
183
183
|
*/
|
|
184
184
|
function lessonProportionsAndAngles(canvasSize = 32, color = '#3f51b5') {
|
|
185
185
|
const mid = Math.floor(canvasSize / 2);
|
|
@@ -193,7 +193,7 @@ function lessonProportionsAndAngles(canvasSize = 32, color = '#3f51b5') {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
/**
|
|
196
|
-
* Lesson 10: Curves —
|
|
196
|
+
* Lesson 10: Curves — C-curves and S-curves.
|
|
197
197
|
*/
|
|
198
198
|
function lessonCurves(canvasSize = 32, color = '#9c27b0') {
|
|
199
199
|
const commands = [];
|
|
@@ -218,7 +218,7 @@ function lessonCurves(canvasSize = 32, color = '#9c27b0') {
|
|
|
218
218
|
}
|
|
219
219
|
|
|
220
220
|
/**
|
|
221
|
-
* Lesson 11: Spiral —
|
|
221
|
+
* Lesson 11: Spiral — Drawing spiral curves.
|
|
222
222
|
*/
|
|
223
223
|
function lessonSpiral(canvasSize = 32, color = '#ff9800') {
|
|
224
224
|
const mid = Math.floor(canvasSize / 2);
|
|
@@ -233,7 +233,7 @@ function lessonSpiral(canvasSize = 32, color = '#ff9800') {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
|
-
* Lesson 12: Strokes —
|
|
236
|
+
* Lesson 12: Strokes — Long strokes, short strokes, thick and thin lines.
|
|
237
237
|
*/
|
|
238
238
|
function lessonStrokes(canvasSize = 32, color = '#2196f3') {
|
|
239
239
|
const margin = Math.floor(canvasSize * 0.1);
|
|
@@ -250,7 +250,7 @@ function lessonStrokes(canvasSize = 32, color = '#2196f3') {
|
|
|
250
250
|
}
|
|
251
251
|
|
|
252
252
|
/**
|
|
253
|
-
* Lesson 13: Parallel & Intersecting —
|
|
253
|
+
* Lesson 13: Parallel & Intersecting Lines — Drawing parallel and intersecting lines.
|
|
254
254
|
*/
|
|
255
255
|
function lessonParallelAndIntersecting(canvasSize = 32, color = '#00bcd4') {
|
|
256
256
|
const margin = Math.floor(canvasSize * 0.1);
|
|
@@ -271,19 +271,19 @@ function lessonParallelAndIntersecting(canvasSize = 32, color = '#00bcd4') {
|
|
|
271
271
|
*/
|
|
272
272
|
function getLessonCatalog() {
|
|
273
273
|
return [
|
|
274
|
-
{ id: 'lines', name: '
|
|
275
|
-
{ id: 'squares', name: '
|
|
276
|
-
{ id: 'circles', name: '
|
|
277
|
-
{ id: 'ellipses', name: '
|
|
278
|
-
{ id: 'triangles', name: '
|
|
279
|
-
{ id: 'polygons', name: '
|
|
280
|
-
{ id: 'composition', name: '
|
|
281
|
-
{ id: 'freehand', name: '
|
|
282
|
-
{ id: 'proportions', name: '
|
|
283
|
-
{ id: 'curves', name: '
|
|
284
|
-
{ id: 'spiral', name: '
|
|
285
|
-
{ id: 'strokes', name: '
|
|
286
|
-
{ id: 'parallel_intersecting', name: '
|
|
274
|
+
{ id: 'lines', name: 'Lines', description: 'Draw horizontal, vertical and diagonal lines', fn: lessonLines },
|
|
275
|
+
{ id: 'squares', name: 'Squares & Rectangles', description: 'Understand right angles and proportions', fn: lessonSquares },
|
|
276
|
+
{ id: 'circles', name: 'Circles', description: 'Learn curves and symmetry', fn: lessonCircles },
|
|
277
|
+
{ id: 'ellipses', name: 'Ellipses', description: 'Understand oval shapes and proportions', fn: lessonEllipses },
|
|
278
|
+
{ id: 'triangles', name: 'Triangles', description: 'Three-point shapes and stability', fn: lessonTriangles },
|
|
279
|
+
{ id: 'polygons', name: 'Polygons', description: 'Multi-sided shapes', fn: lessonPolygons },
|
|
280
|
+
{ id: 'composition', name: 'Composition', description: 'Combine basic shapes to create simple objects', fn: lessonComposition },
|
|
281
|
+
{ id: 'freehand', name: 'Freehand Drawing', description: 'Practice freehand drawing without rulers', fn: lessonFreehand },
|
|
282
|
+
{ id: 'proportions', name: 'Proportions & Angles', description: 'Practice maintaining proportions and angles', fn: lessonProportionsAndAngles },
|
|
283
|
+
{ id: 'curves', name: 'Curves', description: 'C-curves and S-curves', fn: lessonCurves },
|
|
284
|
+
{ id: 'spiral', name: 'Spiral', description: 'Drawing spiral curves', fn: lessonSpiral },
|
|
285
|
+
{ id: 'strokes', name: 'Stroke Types', description: 'Long strokes, short strokes, thick and thin lines', fn: lessonStrokes },
|
|
286
|
+
{ id: 'parallel_intersecting', name: 'Parallel & Intersecting', description: 'Draw parallel and intersecting lines', fn: lessonParallelAndIntersecting },
|
|
287
287
|
];
|
|
288
288
|
}
|
|
289
289
|
|
|
@@ -2,7 +2,7 @@ const shapes = require('./shapes');
|
|
|
2
2
|
const lightShapes = require('./light_shapes');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Lesson: 6 Zones of Light (
|
|
5
|
+
* Lesson: 6 Zones of Light (Sphere)
|
|
6
6
|
* Highlight, Light, Halftone, Core shadow, Reflected light, Cast shadow.
|
|
7
7
|
*/
|
|
8
8
|
function lessonLightZones(canvasSize = 32) {
|
|
@@ -26,7 +26,7 @@ function lessonLightZones(canvasSize = 32) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Lesson: Light Direction & Intensity (
|
|
29
|
+
* Lesson: Light Direction & Intensity (Light direction and plane values)
|
|
30
30
|
* Shows a box with planes facing/turning away from light.
|
|
31
31
|
*/
|
|
32
32
|
function lessonLightDirection(canvasSize = 32) {
|
|
@@ -50,7 +50,7 @@ function lessonLightDirection(canvasSize = 32) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Lesson: Contact Shadow (
|
|
53
|
+
* Lesson: Contact Shadow (Point of contact shadow)
|
|
54
54
|
* The darkest area where an object touches the ground.
|
|
55
55
|
*/
|
|
56
56
|
function lessonContactShadow(canvasSize = 32) {
|
|
@@ -28,7 +28,7 @@ function drawShadedSphere(cx, cy, r, lightAngle) {
|
|
|
28
28
|
const offX = Math.cos(lightAngle);
|
|
29
29
|
const offY = Math.sin(lightAngle);
|
|
30
30
|
|
|
31
|
-
// 1. Cast Shadow
|
|
31
|
+
// 1. Cast Shadow on the ground
|
|
32
32
|
// Drawn first so it's behind the sphere
|
|
33
33
|
// Shift cast shadow away from light
|
|
34
34
|
const castR = Math.floor(r * 1.2);
|
|
@@ -41,26 +41,26 @@ function drawShadedSphere(cx, cy, r, lightAngle) {
|
|
|
41
41
|
// The very edge away from light gets some bounce light
|
|
42
42
|
commands.push(shapes.drawCircle(cx, cy, r, '#78909c', true));
|
|
43
43
|
|
|
44
|
-
// 3. Core Shadow (
|
|
44
|
+
// 3. Core Shadow (main shadow zone)
|
|
45
45
|
// Offset slightly away from light
|
|
46
46
|
const coreR = Math.floor(r * 0.9);
|
|
47
47
|
const coreX = cx - Math.floor(offX * r * 0.1);
|
|
48
48
|
const coreY = cy - Math.floor(offY * r * 0.1);
|
|
49
49
|
commands.push(shapes.drawCircle(coreX, coreY, coreR, '#263238', true));
|
|
50
50
|
|
|
51
|
-
// 4. Halftone (
|
|
51
|
+
// 4. Halftone (mid-tone zone)
|
|
52
52
|
const halfR = Math.floor(r * 0.75);
|
|
53
53
|
const halfX = cx + Math.floor(offX * r * 0.1);
|
|
54
54
|
const halfY = cy + Math.floor(offY * r * 0.1);
|
|
55
55
|
commands.push(shapes.drawCircle(halfX, halfY, halfR, '#546e7a', true));
|
|
56
56
|
|
|
57
|
-
// 5. Light
|
|
57
|
+
// 5. Light zone
|
|
58
58
|
const lightR = Math.floor(r * 0.5);
|
|
59
59
|
const lightX = cx + Math.floor(offX * r * 0.3);
|
|
60
60
|
const lightY = cy + Math.floor(offY * r * 0.3);
|
|
61
61
|
commands.push(shapes.drawCircle(lightX, lightY, lightR, '#90a4ae', true));
|
|
62
62
|
|
|
63
|
-
// 6. Highlight (
|
|
63
|
+
// 6. Highlight (bright spot)
|
|
64
64
|
const highR = Math.floor(r * 0.15);
|
|
65
65
|
const highX = cx + Math.floor(offX * r * 0.5);
|
|
66
66
|
const highY = cy + Math.floor(offY * r * 0.5);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const materialShapes = require('./material_shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson: Shiny vs Matte (Metal vs Plastic/Rubber)
|
|
5
|
+
* Compares high contrast sharp highlights vs low contrast diffused highlights.
|
|
6
|
+
*/
|
|
7
|
+
function lessonMaterialShiny(canvasSize = 32) {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const cx1 = Math.floor(canvasSize * 0.3);
|
|
10
|
+
const cx2 = Math.floor(canvasSize * 0.7);
|
|
11
|
+
const cy = Math.floor(canvasSize / 2);
|
|
12
|
+
const r = Math.floor(canvasSize * 0.2);
|
|
13
|
+
|
|
14
|
+
// 1. Metal Sphere (Left)
|
|
15
|
+
commands.push(...materialShapes.drawMetalSphere(cx1, cy, r));
|
|
16
|
+
|
|
17
|
+
// 2. Matte/Plastic Sphere (Right)
|
|
18
|
+
commands.push(...materialShapes.drawMatteSphere(cx2, cy, r, '#e65100'));
|
|
19
|
+
|
|
20
|
+
return commands;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Lesson: Transparent (Glass)
|
|
25
|
+
* Demonstrates Fresnel effect, sharp reflections, and light transmission.
|
|
26
|
+
*/
|
|
27
|
+
function lessonMaterialGlass(canvasSize = 32) {
|
|
28
|
+
const commands = [];
|
|
29
|
+
const cx = Math.floor(canvasSize / 2);
|
|
30
|
+
const cy = Math.floor(canvasSize / 2);
|
|
31
|
+
const r = Math.floor(canvasSize * 0.35);
|
|
32
|
+
|
|
33
|
+
commands.push(...materialShapes.drawGlassSphere(cx, cy, r));
|
|
34
|
+
|
|
35
|
+
return commands;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Lesson: Texture (Wood vs Stone)
|
|
40
|
+
* Compares organic lines vs rough cracks.
|
|
41
|
+
*/
|
|
42
|
+
function lessonMaterialTexture(canvasSize = 32) {
|
|
43
|
+
const commands = [];
|
|
44
|
+
const cx1 = Math.floor(canvasSize * 0.3);
|
|
45
|
+
const cx2 = Math.floor(canvasSize * 0.7);
|
|
46
|
+
const cy = Math.floor(canvasSize / 2);
|
|
47
|
+
const r = Math.floor(canvasSize * 0.2);
|
|
48
|
+
|
|
49
|
+
// 1. Wood Sphere (Left)
|
|
50
|
+
commands.push(...materialShapes.drawTexturedSphere(cx1, cy, r, 'wood'));
|
|
51
|
+
|
|
52
|
+
// 2. Stone Sphere (Right)
|
|
53
|
+
commands.push(...materialShapes.drawTexturedSphere(cx2, cy, r, 'stone'));
|
|
54
|
+
|
|
55
|
+
return commands;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getMaterialLessonCatalog() {
|
|
59
|
+
return [
|
|
60
|
+
{ id: 'material_shiny', name: 'Metal vs Matte', description: 'Sharp highlights (Metal) vs Soft highlights (Plastic)', fn: lessonMaterialShiny },
|
|
61
|
+
{ id: 'material_glass', name: 'Glass & Transparency', description: 'Fresnel effect and transmitted light', fn: lessonMaterialGlass },
|
|
62
|
+
{ id: 'material_texture', name: 'Texture: Wood vs Stone', description: 'Grain lines vs cracks on a 3D form', fn: lessonMaterialTexture },
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
module.exports = {
|
|
67
|
+
lessonMaterialShiny,
|
|
68
|
+
lessonMaterialGlass,
|
|
69
|
+
lessonMaterialTexture,
|
|
70
|
+
getMaterialLessonCatalog
|
|
71
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
function drawMetalSphere(cx, cy, r) {
|
|
4
|
+
const commands = [];
|
|
5
|
+
|
|
6
|
+
// Base sphere (darkest shadow/environment reflection)
|
|
7
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#263238', true));
|
|
8
|
+
|
|
9
|
+
// High contrast core shadow
|
|
10
|
+
const coreR = Math.floor(r * 0.9);
|
|
11
|
+
commands.push(shapes.drawCircle(cx, cy + Math.floor(r*0.1), coreR, '#1a1a1a', true));
|
|
12
|
+
|
|
13
|
+
// Environment reflection (Ground bounce)
|
|
14
|
+
const envR = Math.floor(r * 0.7);
|
|
15
|
+
commands.push(shapes.drawCircle(cx, cy + Math.floor(r*0.3), envR, '#546e7a', true));
|
|
16
|
+
|
|
17
|
+
// Sharp light zone
|
|
18
|
+
const lightR = Math.floor(r * 0.6);
|
|
19
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.2), cy - Math.floor(r*0.2), lightR, '#cfd8dc', true));
|
|
20
|
+
|
|
21
|
+
// VERY sharp highlight (Tiny and pure white)
|
|
22
|
+
const highR = Math.floor(r * 0.1);
|
|
23
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.4), cy - Math.floor(r*0.4), highR, '#ffffff', true));
|
|
24
|
+
|
|
25
|
+
return commands;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function drawMatteSphere(cx, cy, r, colorBase) {
|
|
29
|
+
const commands = [];
|
|
30
|
+
|
|
31
|
+
// Base sphere (shadow)
|
|
32
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#795548', true)); // Dark brownish shadow
|
|
33
|
+
|
|
34
|
+
// Smooth gradation (Halftone)
|
|
35
|
+
const halfR = Math.floor(r * 0.85);
|
|
36
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.05), cy - Math.floor(r*0.05), halfR, colorBase, true));
|
|
37
|
+
|
|
38
|
+
// Light area
|
|
39
|
+
const lightR = Math.floor(r * 0.6);
|
|
40
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.15), cy - Math.floor(r*0.15), lightR, '#ffcc80', true)); // Lighter tone
|
|
41
|
+
|
|
42
|
+
// Soft highlight (Large and diffused)
|
|
43
|
+
const highR = Math.floor(r * 0.3);
|
|
44
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.25), cy - Math.floor(r*0.25), highR, '#ffe0b2', true));
|
|
45
|
+
|
|
46
|
+
return commands;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function drawGlassSphere(cx, cy, r) {
|
|
50
|
+
const commands = [];
|
|
51
|
+
|
|
52
|
+
// Fresnel effect: Edges are brighter/more reflective than the center
|
|
53
|
+
// Base (Outline & Rim light)
|
|
54
|
+
commands.push(shapes.drawCircle(cx, cy, r, '#80deea', true));
|
|
55
|
+
|
|
56
|
+
// Darker inner area (light passes through)
|
|
57
|
+
const innerR = Math.floor(r * 0.9);
|
|
58
|
+
commands.push(shapes.drawCircle(cx, cy, innerR, '#006064', true));
|
|
59
|
+
|
|
60
|
+
// Transmitted light hitting the back bottom
|
|
61
|
+
const transR = Math.floor(r * 0.7);
|
|
62
|
+
commands.push(shapes.drawCircle(cx + Math.floor(r*0.1), cy + Math.floor(r*0.2), transR, '#4dd0e1', true));
|
|
63
|
+
|
|
64
|
+
// Very center is darkest (least reflection)
|
|
65
|
+
const centerR = Math.floor(r * 0.5);
|
|
66
|
+
commands.push(shapes.drawCircle(cx, cy, centerR, '#00363a', true));
|
|
67
|
+
|
|
68
|
+
// Sharp Highlights (Primary and secondary reflections)
|
|
69
|
+
const highR1 = Math.floor(r * 0.15);
|
|
70
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.4), cy - Math.floor(r*0.4), highR1, '#ffffff', true));
|
|
71
|
+
|
|
72
|
+
const highR2 = Math.floor(r * 0.08);
|
|
73
|
+
commands.push(shapes.drawCircle(cx + Math.floor(r*0.3), cy + Math.floor(r*0.4), highR2, '#e0f7fa', true));
|
|
74
|
+
|
|
75
|
+
return commands;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function drawTexturedSphere(cx, cy, r, type) {
|
|
79
|
+
const commands = [];
|
|
80
|
+
|
|
81
|
+
// Base sphere
|
|
82
|
+
const baseColor = type === 'wood' ? '#8d6e63' : '#9e9e9e';
|
|
83
|
+
commands.push(shapes.drawCircle(cx, cy, r, baseColor, true));
|
|
84
|
+
|
|
85
|
+
// Shadow
|
|
86
|
+
const shadowColor = type === 'wood' ? '#4e342e' : '#616161';
|
|
87
|
+
const shadowR = Math.floor(r * 0.85);
|
|
88
|
+
commands.push(shapes.drawCircle(cx + Math.floor(r*0.1), cy + Math.floor(r*0.1), shadowR, shadowColor, true));
|
|
89
|
+
|
|
90
|
+
// Texture application
|
|
91
|
+
if (type === 'wood') {
|
|
92
|
+
// Wood grain lines curving around the sphere
|
|
93
|
+
const grainColor = '#3e2723';
|
|
94
|
+
for (let i = -2; i <= 2; i++) {
|
|
95
|
+
const rx = r * 0.8;
|
|
96
|
+
const ry = r * 0.3 * Math.abs(i) + r * 0.2;
|
|
97
|
+
commands.push(shapes.drawEllipse(cx, cy + (i * Math.floor(r*0.3)), Math.floor(rx), Math.floor(ry), grainColor, false));
|
|
98
|
+
}
|
|
99
|
+
} else if (type === 'stone') {
|
|
100
|
+
// Stone cracks and rough patches
|
|
101
|
+
const crackColor = '#212121';
|
|
102
|
+
// Simplified cracks using polylines
|
|
103
|
+
commands.push(shapes.drawPolyline([
|
|
104
|
+
{x: cx - Math.floor(r*0.5), y: cy - Math.floor(r*0.5)},
|
|
105
|
+
{x: cx - Math.floor(r*0.2), y: cy - Math.floor(r*0.1)},
|
|
106
|
+
{x: cx + Math.floor(r*0.3), y: cy - Math.floor(r*0.2)},
|
|
107
|
+
{x: cx + Math.floor(r*0.6), y: cy + Math.floor(r*0.1)}
|
|
108
|
+
], crackColor));
|
|
109
|
+
|
|
110
|
+
commands.push(shapes.drawPolyline([
|
|
111
|
+
{x: cx - Math.floor(r*0.2), y: cy - Math.floor(r*0.1)},
|
|
112
|
+
{x: cx, y: cy + Math.floor(r*0.4)},
|
|
113
|
+
{x: cx - Math.floor(r*0.4), y: cy + Math.floor(r*0.7)}
|
|
114
|
+
], crackColor));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Soft Highlight over texture
|
|
118
|
+
const highColor = type === 'wood' ? '#d7ccc8' : '#e0e0e0';
|
|
119
|
+
const highR = Math.floor(r * 0.4);
|
|
120
|
+
commands.push(shapes.drawCircle(cx - Math.floor(r*0.3), cy - Math.floor(r*0.3), highR, highColor, true));
|
|
121
|
+
|
|
122
|
+
return commands;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
drawMetalSphere,
|
|
127
|
+
drawMatteSphere,
|
|
128
|
+
drawGlassSphere,
|
|
129
|
+
drawTexturedSphere
|
|
130
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const materialLessons = require('./material_lessons');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register all material & surface rendering tools on the MCP server.
|
|
7
|
+
* @param {McpServer} server
|
|
8
|
+
*/
|
|
9
|
+
function register(server) {
|
|
10
|
+
registerTool(server, 'art_tree_lesson_material_shiny',
|
|
11
|
+
'Lesson: Materials - Metal vs Matte (High contrast vs diffused highlights)',
|
|
12
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
13
|
+
async (p) => {
|
|
14
|
+
const commands = materialLessons.lessonMaterialShiny(p.canvasSize);
|
|
15
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
16
|
+
return { content: [{ type: 'text', text: `✓ Metal vs Matte material lesson completed` }] };
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
registerTool(server, 'art_tree_lesson_material_glass',
|
|
20
|
+
'Lesson: Materials - Glass (Transparency, Fresnel effect)',
|
|
21
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
22
|
+
async (p) => {
|
|
23
|
+
const commands = materialLessons.lessonMaterialGlass(p.canvasSize);
|
|
24
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
25
|
+
return { content: [{ type: 'text', text: `✓ Glass material lesson completed` }] };
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
registerTool(server, 'art_tree_lesson_material_texture',
|
|
29
|
+
'Lesson: Materials - Texture (Wood grain vs Stone cracks)',
|
|
30
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
31
|
+
async (p) => {
|
|
32
|
+
const commands = materialLessons.lessonMaterialTexture(p.canvasSize);
|
|
33
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
34
|
+
return { content: [{ type: 'text', text: `✓ Wood vs Stone texture lesson completed` }] };
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
registerTool(server, 'art_tree_lesson_material_catalog',
|
|
38
|
+
'Get the catalog of all available Material lessons',
|
|
39
|
+
{},
|
|
40
|
+
() => {
|
|
41
|
+
const catalog = materialLessons.getMaterialLessonCatalog();
|
|
42
|
+
const text = catalog.map(l =>
|
|
43
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
44
|
+
).join('\n');
|
|
45
|
+
return { content: [{ type: 'text', text: `📚 Material Lessons:\n\n${text}` }] };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { register };
|
|
@@ -89,7 +89,7 @@ function lesson3PointPerspective(canvasSize = 32, color = '#ff9800') {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
|
-
* Lesson: Foreshortening (
|
|
92
|
+
* Lesson: Foreshortening (Depth foreshortening)
|
|
93
93
|
* Drawing a cylinder pointing directly at the viewer
|
|
94
94
|
*/
|
|
95
95
|
function lessonForeshortening(canvasSize = 32, color = '#9c27b0') {
|
|
@@ -37,7 +37,7 @@ function register(server) {
|
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
registerTool(server, 'art_tree_lesson_persp_foreshorten',
|
|
40
|
-
'Lesson: Perspective - Foreshortening (
|
|
40
|
+
'Lesson: Perspective - Foreshortening (Depth foreshortening)',
|
|
41
41
|
{ canvasSize: z.number().int().min(16).max(64).default(32), color: hexColor.default('#9c27b0') },
|
|
42
42
|
async (p) => {
|
|
43
43
|
const commands = perspLessons.lessonForeshortening(p.canvasSize, p.color);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
const skyShapes1 = require('./sky_shapes_1');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lesson 1: Sun Shapes (Hình dạng mặt trời)
|
|
5
|
+
* Compares Basic, Soft, and Rays
|
|
6
|
+
*/
|
|
7
|
+
function lessonSunShapes(canvasSize = 32) {
|
|
8
|
+
const commands = [];
|
|
9
|
+
const y = Math.floor(canvasSize / 2);
|
|
10
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
11
|
+
|
|
12
|
+
// Left: Basic
|
|
13
|
+
commands.push(...skyShapes1.drawSunBasic(Math.floor(canvasSize * 0.2), y, r));
|
|
14
|
+
// Center: Soft
|
|
15
|
+
commands.push(...skyShapes1.drawSunSoft(Math.floor(canvasSize * 0.5), y, r));
|
|
16
|
+
// Right: Rays
|
|
17
|
+
commands.push(...skyShapes1.drawSunRays(Math.floor(canvasSize * 0.8), y, r));
|
|
18
|
+
|
|
19
|
+
return commands;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Lesson 2: Sunset Colors (Màu sắc buổi chiều)
|
|
24
|
+
* Shows the progression of colors from blue to yellow
|
|
25
|
+
*/
|
|
26
|
+
function lessonSunsetColors(canvasSize = 32) {
|
|
27
|
+
const commands = [];
|
|
28
|
+
|
|
29
|
+
// The classic sunset gradient: Blue -> Purple -> Pink -> Orange -> Yellow (top to bottom)
|
|
30
|
+
const sunsetPalette = [
|
|
31
|
+
'#3f51b5', // Blue
|
|
32
|
+
'#9c27b0', // Purple
|
|
33
|
+
'#e91e63', // Pink
|
|
34
|
+
'#ff9800', // Orange
|
|
35
|
+
'#ffeb3b' // Yellow
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
commands.push(...skyShapes1.drawSkyGradient(0, 0, canvasSize, canvasSize, sunsetPalette));
|
|
39
|
+
|
|
40
|
+
return commands;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lesson 3: Sunset Sky (Bầu trời hoàng hôn)
|
|
45
|
+
* Combines the gradient sky with a soft sun setting at the bottom
|
|
46
|
+
*/
|
|
47
|
+
function lessonSunsetSky(canvasSize = 32) {
|
|
48
|
+
const commands = [];
|
|
49
|
+
|
|
50
|
+
// 1. Draw the Sky
|
|
51
|
+
const sunsetPalette = [
|
|
52
|
+
'#1a237e', // Dark Blue
|
|
53
|
+
'#512da8', // Deep Purple
|
|
54
|
+
'#c2185b', // Magenta
|
|
55
|
+
'#ff5722', // Deep Orange
|
|
56
|
+
'#ffb300' // Amber
|
|
57
|
+
];
|
|
58
|
+
commands.push(...skyShapes1.drawSkyGradient(0, 0, canvasSize, canvasSize, sunsetPalette));
|
|
59
|
+
|
|
60
|
+
// 2. Draw the Sun (Soft glowing sun near the bottom)
|
|
61
|
+
const cx = Math.floor(canvasSize / 2);
|
|
62
|
+
const cy = Math.floor(canvasSize * 0.8);
|
|
63
|
+
const r = Math.floor(canvasSize * 0.15);
|
|
64
|
+
commands.push(...skyShapes1.drawSunSoft(cx, cy, r));
|
|
65
|
+
|
|
66
|
+
return commands;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getSkyLesson1Catalog() {
|
|
70
|
+
return [
|
|
71
|
+
{ id: 'sky_sun_shapes', name: 'Sun Shapes', description: 'Basic vs Soft vs Rays', fn: lessonSunShapes },
|
|
72
|
+
{ id: 'sky_sunset_colors', name: 'Sunset Colors', description: 'Gradient bands from Blue to Yellow', fn: lessonSunsetColors },
|
|
73
|
+
{ id: 'sky_sunset_sky', name: 'Sunset Sky', description: 'Combining gradient sky with a soft sun', fn: lessonSunsetSky },
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = {
|
|
78
|
+
lessonSunShapes,
|
|
79
|
+
lessonSunsetColors,
|
|
80
|
+
lessonSunsetSky,
|
|
81
|
+
getSkyLesson1Catalog
|
|
82
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const shapes = require('./shapes');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Draws a basic solid sun
|
|
5
|
+
*/
|
|
6
|
+
function drawSunBasic(cx, cy, r) {
|
|
7
|
+
return [shapes.drawCircle(cx, cy, r, '#ffeb3b', true)];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Draws a soft glowing sun using concentric circles with decreasing opacity
|
|
12
|
+
* Since we don't have true opacity in standard shapes yet, we use color stepping
|
|
13
|
+
* (white -> light yellow -> yellow -> orange)
|
|
14
|
+
*/
|
|
15
|
+
function drawSunSoft(cx, cy, r) {
|
|
16
|
+
const commands = [];
|
|
17
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 1.5), '#ffe082', true)); // Outer glow
|
|
18
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 1.2), '#ffeb3b', true)); // Mid glow
|
|
19
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 0.8), '#fff59d', true)); // Inner glow
|
|
20
|
+
commands.push(shapes.drawCircle(cx, cy, Math.floor(r * 0.4), '#ffffff', true)); // Core
|
|
21
|
+
return commands;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Draws a sun with distinct rays
|
|
26
|
+
*/
|
|
27
|
+
function drawSunRays(cx, cy, r) {
|
|
28
|
+
const commands = [];
|
|
29
|
+
const color = '#ffeb3b';
|
|
30
|
+
// Central body
|
|
31
|
+
commands.push(shapes.drawCircle(cx, cy, r, color, true));
|
|
32
|
+
|
|
33
|
+
// Outer rays
|
|
34
|
+
const rayL = Math.floor(r * 2);
|
|
35
|
+
for (let i = 0; i < 12; i++) {
|
|
36
|
+
const a = (i / 12) * 2 * Math.PI;
|
|
37
|
+
const x1 = Math.round(cx + (r + 2) * Math.cos(a));
|
|
38
|
+
const y1 = Math.round(cy + (r + 2) * Math.sin(a));
|
|
39
|
+
const x2 = Math.round(cx + rayL * Math.cos(a));
|
|
40
|
+
const y2 = Math.round(cy + rayL * Math.sin(a));
|
|
41
|
+
commands.push(shapes.drawLine(x1, y1, x2, y2, color));
|
|
42
|
+
}
|
|
43
|
+
return commands;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Draws a simulated gradient sky using horizontal bands of color
|
|
48
|
+
* @param {Array<string>} colors - Array of hex colors from top to bottom
|
|
49
|
+
*/
|
|
50
|
+
function drawSkyGradient(x, y, w, h, colors) {
|
|
51
|
+
const commands = [];
|
|
52
|
+
const bandHeight = Math.ceil(h / colors.length);
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < colors.length; i++) {
|
|
55
|
+
const bandY = y + i * bandHeight;
|
|
56
|
+
// To ensure we cover the total height, the last band fills the rest
|
|
57
|
+
const bh = (i === colors.length - 1) ? (y + h - bandY) : bandHeight;
|
|
58
|
+
commands.push(shapes.drawRectangle(x, bandY, w, bh, colors[i], true));
|
|
59
|
+
}
|
|
60
|
+
return commands;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
drawSunBasic,
|
|
65
|
+
drawSunSoft,
|
|
66
|
+
drawSunRays,
|
|
67
|
+
drawSkyGradient
|
|
68
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { z } = require('zod');
|
|
2
|
+
const { registerTool, sendCommand } = require('../../core/server');
|
|
3
|
+
const skyLessons1 = require('./sky_lessons_1');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Register all sky and sun tools on the MCP server.
|
|
7
|
+
* @param {McpServer} server
|
|
8
|
+
*/
|
|
9
|
+
function register(server) {
|
|
10
|
+
// Phase 1 Tools
|
|
11
|
+
registerTool(server, 'art_tree_lesson_sky_sun_shapes',
|
|
12
|
+
'Lesson: Sun & Sky - Compare Basic, Soft, and Rays sun shapes',
|
|
13
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
14
|
+
async (p) => {
|
|
15
|
+
const commands = skyLessons1.lessonSunShapes(p.canvasSize);
|
|
16
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
17
|
+
return { content: [{ type: 'text', text: `✓ Sun Shapes lesson completed` }] };
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
registerTool(server, 'art_tree_lesson_sky_sunset_colors',
|
|
21
|
+
'Lesson: Sun & Sky - Draw a sunset gradient (Blue to Yellow)',
|
|
22
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
23
|
+
async (p) => {
|
|
24
|
+
const commands = skyLessons1.lessonSunsetColors(p.canvasSize);
|
|
25
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
26
|
+
return { content: [{ type: 'text', text: `✓ Sunset Colors lesson completed` }] };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
registerTool(server, 'art_tree_lesson_sky_sunset_sky',
|
|
30
|
+
'Lesson: Sun & Sky - Combine gradient sky with a soft setting sun',
|
|
31
|
+
{ canvasSize: z.number().int().min(16).max(64).default(32) },
|
|
32
|
+
async (p) => {
|
|
33
|
+
const commands = skyLessons1.lessonSunsetSky(p.canvasSize);
|
|
34
|
+
await Promise.all(commands.map(cmd => sendCommand(cmd)));
|
|
35
|
+
return { content: [{ type: 'text', text: `✓ Sunset Sky lesson completed` }] };
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
registerTool(server, 'art_tree_lesson_sky_catalog',
|
|
39
|
+
'Get the catalog of all available Sun & Sky lessons',
|
|
40
|
+
{},
|
|
41
|
+
() => {
|
|
42
|
+
const cat1 = skyLessons1.getSkyLesson1Catalog();
|
|
43
|
+
const catalog = [...cat1];
|
|
44
|
+
const text = catalog.map(l =>
|
|
45
|
+
` • ${l.id}: ${l.name} — ${l.description}`
|
|
46
|
+
).join('\n');
|
|
47
|
+
return { content: [{ type: 'text', text: `📚 Sun & Sky Lessons:\n\n${text}` }] };
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { register };
|