Skip to content

Instantly share code, notes, and snippets.

@LABCAT
Forked from dribnet/.block
Created July 17, 2017 01:02
Show Gist options
  • Select an option

  • Save LABCAT/2dda25478b5b27c3640d1da419aa98e6 to your computer and use it in GitHub Desktop.

Select an option

Save LABCAT/2dda25478b5b27c3640d1da419aa98e6 to your computer and use it in GitHub Desktop.
17.2.MDDN342 PS1
license: mit
function resetFocusedRandom() {
return Math.seedrandom(arguments);
}
function focusedRandom(min, max, focus, mean) {
// console.log("hello")
if(max === undefined) {
max = min;
min = 0;
}
if(focus === undefined) {
focus = 1.0;
}
if(mean === undefined) {
mean = (min + max) / 2.0;
}
if(focus == 0) {
return d3.randomUniform(min, max)();
}
else if(focus < 0) {
focus = -1 / focus;
}
sigma = (max - mean) / focus;
val = d3.randomNormal(mean, sigma)();
if (val > min && val < max) {
return val;
}
return d3.randomUniform(min, max)();
}
// note: this file is poorly named - it can generally be ignored.
// helper functions below for supporting blocks/purview
function saveBlocksImages(doZoom) {
if(doZoom == null) {
doZoom = false;
}
// generate 960x500 preview.jpg of entire canvas
// TODO: should this be recycled?
var offscreenCanvas = document.createElement('canvas');
offscreenCanvas.width = 960;
offscreenCanvas.height = 500;
var context = offscreenCanvas.getContext('2d');
// background is flat white
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 960, 500);
context.drawImage(this.canvas, 0, 0, 960, 500);
// save to browser
var downloadMime = 'image/octet-stream';
var imageData = offscreenCanvas.toDataURL('image/jpeg');
imageData = imageData.replace('image/jpeg', downloadMime);
p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg');
// generate 230x120 thumbnail.png centered on mouse
offscreenCanvas.width = 230;
offscreenCanvas.height = 120;
// background is flat white
context = offscreenCanvas.getContext('2d');
context.fillStyle="#FFFFFF";
context.fillRect(0, 0, 230, 120);
if(doZoom) {
// pixelDensity does the right thing on retina displays
var pd = this._pixelDensity;
var sx = pd * mouseX - pd * 230/2;
var sy = pd * mouseY - pd * 120/2;
var sw = pd * 230;
var sh = pd * 120;
// bounds checking - just displace if necessary
if (sx < 0) {
sx = 0;
}
if (sx > this.canvas.width - sw) {
sx = this.canvas.width - sw;
}
if (sy < 0) {
sy = 0;
}
if (sy > this.canvas.height - sh) {
sy = this.canvas.height - sh;
}
// save to browser
context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120);
}
else {
// now scaledown
var full_width = this.canvas.width;
var full_height = this.canvas.height;
context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120);
}
imageData = offscreenCanvas.toDataURL('image/png');
imageData = imageData.replace('image/png', downloadMime);
p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png');
}

PS1 MDDN 342 2017

Robots vs Monsters

My final submission represents a battle for the earth between robots and monsters. There is a scoreboard in the bottom left hand corner displaying the current score. You can even influence the result by using your center (monster) and right (robot) buttons to help out your favourite side.

Project Summary

In the first stage of the assignment I created two portraits of cartoon robots. My aim was to replicate them as closely as possible and managed to achieve this very well. I have continued to use the robot theme throughout the rest of the project.

In the drawing styles section of the assignment I choose to draw a bear, a robot and a monster. The bear was inspired by various cartoon bears but for the robot and monster I was aiming to create original characters. The monster is completely original however the robot still contains a heavy influence from the one of the robots (Bender) i drew in part one.

For the randomize part of the assignment I started with monsters first as I felt that was my strongest character. It worked quite well but I decided that I wanted to use two characters and came up with the theme of "Robot vs Monsters". I didn't need to change the ranges of my parameters too much as they worked very well. However I did need to reduce the scale of both faces so they weren't too big.

For the distribution part of the assignment my aim was to make the generated population more likely to consist of the traits that I liked the most. One feature of this part that I am particularly happy with was the use of the focusedRandom function to influence the eye size of the monsters. I used a variable mean value basic on the monsters size which makes its more likely for a bigger monster to have smaller eyes and vice versa for smaller monsters.

For the final part of the assignment was heavily influenced by some code that I had to write to ensure loading performance was still acceptable. Drawing the monster face is quite a heavy process and I wanted to have a lot more faces on the canvas. To accomplish this I had to implement some code that outputs several faces at a time rather than all of them.

The result was very interesting and inspired the idea that the composition could also act as a game of sorts. So I added a scoreboard which keeps track of the number of robots and monsters on the canvas. The layout of the composition is actually a grid but the order in which the faces are drawn at the different co-ordinates is totally random. I think this makes the display process much more interesting than simply drawing them from left to right and top to bottom.

<head>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/p5.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/p5.js/0.5.11/addons/p5.dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js"></script>
<script src="https://d3js.org/d3-random.v1.min.js"></script>
<!--
<script src="p5.js"></script>
<script src="p5.dom.js"></script>
<script src="seedrandom.min.js"></script>
<script src="d3-random.v1.min.js"></script>
-->
<script language="javascript" type="text/javascript" src=".purview_helper.js"></script>
<script language="javascript" type="text/javascript" src=".focused_random.js"></script>
<script language="javascript" type="text/javascript" src="sketch.js"></script>
<style>
body { padding: 0; margin: 0; }
.inner { position: absolute; }
#controls {
font: 300 12px "Helvetica Neue";
padding: 5;
margin: 5;
background: #f0f0f0;
opacity: 0.0;
-webkit-transition: opacity 0.2s ease;
-moz-transition: opacity 0.2s ease;
-o-transition: opacity 0.2s ease;
-ms-transition: opacity 0.2s ease;
}
#controls:hover { opacity: 0.9; }
</style>
</head>
<body style="background-color:white" oncontextmenu="return false;">
<div class="outer">
<div class="inner">
<div id="canvasContainer"></div>
</div>
</div>
</table>
</body>
{
"commits": [
{
"sha": "1e88062f212f6b5741b4954f30f952fc6976e77e",
"name": "final"
},
{
"sha": "cfe9a3a183c55b5844d5d284971d09263bb596bd",
"name": "distribution"
},
{
"sha": "16e6a335c63d367b40b1dfc277dc0f5ea68de95b",
"name": "randomize"
},
{
"sha": "8303a2d88e43427795431be30c7ddc8cc2442d45",
"name": "random_monsters"
},
{
"sha": "559d9691b55f1626eb62c4efee74c5992cc2b72c",
"name": "drawing_styles"
},
{
"sha": "3aa6d46a6701dd2f244eaa910bad32d997bbee62",
"name": "drawing_styles_3"
},
{
"sha": "539e2602788caf9f3b128804c52b12b4593c3e0a",
"name": "drawing_styles_2"
},
{
"sha": "4c7dd40dcb12c73bb71b02a83b7840586d035d47",
"name": "drawing_styles_1"
},
{
"sha": "85cc0acaf72e6db9f40d92a7bd1ce291d55100aa",
"name": "portraits"
}
]
}
var canvasWidth = 960;
var canvasHeight = 500;
var button;
var curRandomSeed;
//background colour
var bg_color1 = [238, 238, 238];
//arrays of random values used for drawing the face shape/hair of the monster face
var randomHue = [];
var randomSaturation = [];
var randomBrightness = [];
var randomLength = [];
//co-ordinates array an associated pointer
var coOrdsArray = [];
var coOrdsArrayPointer = [];
var coOrdsCount = 0;
//count the monsters and robots
var robotCount = 0;
var monsterCount = 0;
var totalFaceCount = 0;
function keyTyped() {
if (key == '!') {
saveBlocksImages();
}
else if (key == '@') {
saveBlocksImages(true);
}
}
var robotImg;
var monsterImg;
function preload() {
robotImg = loadImage("robot.png");
monsterImg = loadImage("monster.png");
}
function setup () {
// create the drawing canvas, save the canvas element
var main_canvas = createCanvas(canvasWidth, canvasHeight);
main_canvas.parent('canvasContainer');
noLoop();
curRandomSeed = int(focusedRandom(0, 100));
//setup arrays required for random hue, saturation, brightness and length values used for the monster face
for(var i=0; i<720; i++){
randomHue[i] = random(0, 350);
randomSaturation[i] = random(25, 75);
randomBrightness[i] = random(50, 100);
randomLength[i] = random(0, 120);
}
//set up co-ordinates array
var w = canvasWidth / 12;
var h = canvasHeight / 6;
for(var i=0; i<12; i++) {
for(var j=0; j<6; j++) {
var x = w/2 + w*i;
var y = h/2 + h*j;
var coOrd = { 'x': x, 'y': y }
coOrdsArray.push(coOrd);
coOrdsArrayPointer.push(coOrdsCount);
coOrdsCount++;
}
}
// rotation in degrees
angleMode(DEGREES);
}
/*
* listen for mouse click
* left click calls the changeRandomSeed function
* right click creates a new robot
* center click creates a new monster
*/
function mousePressed() {
var face = '';
if (mouseButton == LEFT) {
//the reset process can only be done once all faces have been drawn
if(totalFaceCount >= coOrdsArray.length){
changeRandomSeed();
clearInterval(timerId);
timerIsRunning = false;
}
}
if (mouseButton == RIGHT) {
face = 'robot';
}
if (mouseButton == CENTER) {
face = 'monster';
}
if(face){
drawFace(mouseX, mouseY, face);
}
}
/* changes the current random seed
* resets robot count, monster count and total face count
* resets coOrdsArrayPointer array
* resets scoreboard
* then calls redraw
*/
function changeRandomSeed() {
//prevent double ups from faces that haven't completed being drawn yet
clear();
curRandomSeed = curRandomSeed + 1;
robotCount = 0;
monsterCount = 0;
totalFaceCount = 0;
coOrdsArrayPointer = [];
resetCoOrdsArrayPointer();
resetScoreBoard();
redraw();
}
/*
* refills the array of pointers used to determine grid coordinates
* this needs to done before redraw is called.
*/
function resetCoOrdsArrayPointer(){
for(var i=0; i<coOrdsCount; i++) {
coOrdsArrayPointer.push(i);
}
}
//prevent multiple timers from running
var timerIsRunning = false;
var timerId = 0;
function startResetTimer(){
var countDown = 10;
if(!timerIsRunning){
timerIsRunning = true;
print('timer is running with ' + countDown + ' seconds to go');
timerId = setInterval(
function(){
if(countDown == 0) {
changeRandomSeed();
clearInterval(timerId);
timerIsRunning = false;
}
countDown--;
},
1000
);
}
}
function draw () {
resetFocusedRandom(curRandomSeed);
noStroke();
background(bg_color1);
resetScoreBoard();
for(var i=0; i<coOrdsArray.length; i++) {
var pointer = random(coOrdsArrayPointer);
var index = coOrdsArrayPointer.indexOf(pointer);
if (index > -1) {
coOrdsArrayPointer.splice(index, 1);
}
//this is used to prevent processing from waiting for all faces to be drawn before displaying them
setTimeout(
function(x, y){
return function() { drawFace(x, y) };
}(coOrdsArray[pointer]['x'], coOrdsArray[pointer]['y']),
0
);
}
}
/*
* JSON object consisting of six hue ranges
* used to ensure each robot has a unique hue
* the third element of each array is the midpoint of the range
* which is used to set the mean value for the focusedRandom function
*/
var robotHueRanges = {
1 : [-20, 20, 0],
2 : [21, 60, 40],
3 : [61, 100, 80],
4 : [101, 140, 120],
5 : [141, 180, 160],
6 : [181, 220, 200],
7 : [221, 260, 240],
8 : [261, 300, 280],
9 : [301, 340, 320]
}
//used to get the values from the robotHueRanges object
var robotHuePointer = 0;
/**
* This function determines what value will be used for the discrete setting of each face (eyes or antennas)
* @param {String} type - the type of discrete setting, possible values are 'eyes' or 'antennas'
* @return {Number} - the value to use as the discrete setting
*/
function getRandomNumberForDiscreteSetting(type = 'eyes') {
//the probabilities for the two possible types
var probs = {
'eyes' : [50, 60, 75],
'antennas' : [10, 25, 75]
}
var random_result = focusedRandom(0, 100);
if(random_result < probs[type][0]) {
return 1;
}
else if(random_result < probs[type][1]) {
return 2;
}
else if(random_result < probs[type][2]) {
return 3;
}
else {
return 4;
}
}
/**
* determines whether to draw a monster or robot face
* there will be a maximum of six robots and each robot will have a unique colour
* @param {Number} x - x position where the face will be drawn
* @param {Number} y - y position where the face will be drawn
*/
function drawFace (x, y, selectedFace = false) {
if(robotHuePointer > 8){
robotHuePointer = 0;
}
var face = random(['monster', 'robot']);
if(selectedFace){
face = selectedFace;
}
if(face == "monster"){
//number of eyes
var num_of_eyes = getRandomNumberForDiscreteSetting('eyes');
var hue = focusedRandom(120, 330);
//the amount zig zig present in the pattern should lean towards a smaller value
var zigzag = focusedRandom(1, 20, 10, 3);
var size_adjuster = focusedRandom(0.2, 0.5, 2, 0.2);
//the smaller the monster is the bigger its eyes should be
var eye_size = focusedRandom(20, 80, 100, 140 - (size_adjuster * 200));
var tilt_value = focusedRandom(-15, 45);
drawMonsterFace(x, y, num_of_eyes, eye_size, hue, zigzag, size_adjuster, tilt_value);
}
else if(face == "robot"){
robotHuePointer++;
//number of antennas
var num_of_antennas = getRandomNumberForDiscreteSetting('antennas');
//the face height should be closer to the center of the range
var face_height = focusedRandom(0, 200, 8, 80);
//the hue should be focused toward the center of this robots hue range
var hue = focusedRandom(robotHueRanges[robotHuePointer][0], robotHueRanges[robotHuePointer][1], 10, robotHueRanges[robotHuePointer][2]);
//the actual range of values for face_shape is 0-200 but I want the value for face shape to be either close to 0 or 200
//so I have focused the random value around a mean of 200 between a range of 100 and 300
var face_shape = focusedRandom(100, 300, 10, 200);
//and if the value ends up being greater than 200
if(face_shape > 200){
//make it closer to zero
face_shape = face_shape - 200;
}
var mouth_style = focusedRandom(50, 10);
var eye_distance = focusedRandom(50, 0);
var tilt_value = focusedRandom(30, -30);
var size_adjuster = focusedRandom(0.2, 0.5, 2, 0.2);
drawRobotFace(x, y, num_of_antennas, face_height, hue, face_shape, mouth_style, eye_distance, tilt_value, size_adjuster);
}
}
/**
* draws a robot face
*/
function drawRobotFace(x, y, num_antennas, face_height, hue, face_shape, mouth_style, eye_distance, tilt_value, size_adjuster) {
push();
rectMode(CENTER);
translate(x, y);
rotate(tilt_value);
colorMode(HSB);
scale(size_adjuster);
stroke(hue, 50, 90);
fill(hue, 90, 50);
var minimizer = 0.5;
//antenna
var yVariation = ((300 + face_height) * minimizer) /2;
var xVariation = (300 * minimizer) /2;
if(num_antennas == 1 || num_antennas == 3){
triangle(-20, -20, 0, -50 - yVariation, 20, -20);
ellipse(0, 10 - yVariation, 40, 40);
ellipse(0, -50 - yVariation, 15, 15);
}
if(num_antennas == 2 || num_antennas == 4){
triangle(-10, -10, -70, -45 - yVariation, 10, -10);
ellipse(-70, -45 - yVariation, 10, 10);
triangle(-10, -10, 70, -45 - yVariation, 10, -10);
ellipse(70, -45 - yVariation, 10, 10);
}
if(num_antennas == 3 || num_antennas == 4){
//left antenna
stroke(hue, 90, 50);
fill(hue, 50, 90);
strokeWeight(0);
rect(-xVariation, -40, 130, 5);
noFill();
strokeWeight(4);
ellipse(-xVariation - 40, -40, 12, 50);
ellipse(-xVariation - 60, -40, 8, 30);
//right antenna
stroke(hue, 90, 50);
fill(hue, 50, 90);
strokeWeight(0);
rect(xVariation, -40, 130, 5, 20, 20);
noFill();
strokeWeight(4);
ellipse(xVariation + 40, -40, 12, 50);
ellipse(xVariation + 60, -40, 8, 30);
}
stroke(hue, 50, 90);
strokeWeight(1);
fill(hue, 90, 50);
//head
var bottomCorners = face_shape - 50;
bottomCorners = bottomCorners > 0 ? bottomCorners : 0;
if(face_shape <= 100) {
rect(0, 0, 300 * minimizer, (300 + face_height) * minimizer, face_shape, face_shape, bottomCorners, bottomCorners);
}
else {
bottomCorners = bottomCorners - ((face_shape - 100) * 2);
bottomCorners = bottomCorners > 0 ? bottomCorners : 0;
if(face_shape >= 150){
quad(xVariation - 5, yVariation*0.25, xVariation + ((face_shape - 150) / 10), yVariation*0.25, xVariation + (face_shape - 150), yVariation - 5, xVariation - 5, yVariation - 5);
quad(-xVariation - 1, yVariation*0.25, -xVariation - 1 - ((face_shape - 150) / 10), yVariation*0.25, -xVariation - (face_shape - 150), yVariation - 5, -xVariation + 1, yVariation - 5);
}
rect(0, 0, 300 * minimizer, (300 + face_height) * minimizer, face_shape, face_shape, bottomCorners, bottomCorners);
}
//eyes
var i=40;
var fillObj = {
40: [0, 0, 100],
16: [0, 0, 0],
8: [hue, 50, 90]
}
var xPos=(25 + eye_distance), yPos=-40;
//eyes holder
rect(0, yPos, 110 + (eye_distance*2), 60, 45, 45, 45, 45);
fill(0);
rect(0, yPos, 100 + (eye_distance*2), 50, 45, 45, 45, 45);
stroke(0);
while(i>=8){
if(i == 40 || i == 16){
fill(fillObj[i][0], fillObj[i][1],fillObj[i][2]);
//left eye
ellipse(-xPos, yPos, i, i);
//right_eye
ellipse(xPos, yPos, i, i);
}
if(i == 8){
fill(fillObj[i][0], fillObj[i][1],fillObj[i][2]);
//left eye
rect(-xPos, yPos, i, i);
//right_eye
rect(xPos, yPos, i, i);
}
i=i-8;
}
// mouth
noFill();
stroke(hue, 30, 90);
strokeWeight(2);
var i=40, j=50;
if(mouth_style <= 20){
i=35, j=55;
}
while(i<=j){
line(-mouth_style, i, mouth_style, i);
if(mouth_style < 30){
line(-50, i, -35, i);
line(50, i, 35, i);
}
if(mouth_style < 20){
line(-30, i, -mouth_style - 5, i);
line(30, i, mouth_style + 5, i);
}
i = i + 5;
}
colorMode(RGB);
rectMode(CORNER);
pop();
updateScoreBoard('robot');
}
/**
* draws a monster face
*/
function drawMonsterFace(x, y, num_of_eyes, eye_size, hue, zigzag, size_adjuster, tilt_value) {
//positions of the four eyes
var eyePositions = {
0: {
'x': 0,
'y': 0
},
1: {
'x': -30,
'y': 0
},
2: {
'x': 35,
'y': -120
},
3: {
'x': -95,
'y': -80
},
}
if(num_of_eyes == 2 || num_of_eyes == 4){
eyePositions[0] = {'x': 25,'y': -15}
}
//used to keep the face in the centre if its space
var xAdjuster = 107;
push();
translate(x-xAdjuster * size_adjuster, y);
rotate(tilt_value);
//small adjustment for rotation
translate(0, -tilt_value);
colorMode(HSB)
scale(size_adjuster);
//eye necks for first and forth faces - needs to drawn before the face
strokeWeight(7);
stroke(hue,80,40);
if(num_of_eyes > 2){
line(xAdjuster, 0, xAdjuster + eyePositions[2]['x'], eyePositions[2]['y']);
line(xAdjuster, 0, xAdjuster + eyePositions[3]['x'], eyePositions[3]['y']);
}
noStroke();
//face
for(var i=0; i<720; i++){
//create a random fill colour
if(i % 10 == 0){
fill(randomHue[i], randomSaturation[i], randomBrightness[i]);
}
else {
fill(hue, randomSaturation[i], randomBrightness[i]);
}
push();
translate(110, 10);
var degree = map(i, 0, 720, 0, 360);
rotate(i);
var j = 0;
while(j < (randomLength[i])) {
var yPos = j % zigzag;
if(yPos > (zigzag/2)){
yPos = -yPos + (zigzag/2);
}
ellipse(j, yPos, 2, 2);
j++;
}
pop();
}
//eyes
for(var i=0; i<num_of_eyes; i++){
var arrayPointer = i;
if(num_of_eyes == 3 && i == 1){
arrayPointer = 3;
}
strokeWeight(7);
stroke(hue,80,40);
fill(hue,80,40);
ellipse((xAdjuster+eyePositions[arrayPointer]['x']), eyePositions[arrayPointer]['y'], eye_size+2, eye_size+2);
stroke(hue, 40, 80);
strokeWeight(3);
fill(0,0,100);
ellipse(xAdjuster+eyePositions[arrayPointer]['x'], eyePositions[arrayPointer]['y'], eye_size, eye_size);
strokeWeight(0);
fill(hue,80,40);
ellipse(xAdjuster+eyePositions[arrayPointer]['x'], eyePositions[arrayPointer]['y'], eye_size/2, eye_size/2 );
fill(0,0,100);
ellipse(xAdjuster+eyePositions[arrayPointer]['x'] + eye_size/6, eyePositions[arrayPointer]['y'], eye_size/8, eye_size/8 );
}
//put the mouth on an angle
translate(-10, 20);
rotate(-12);
strokeWeight(5);
stroke(hue,80,40);
//mouth
fill(0,100,0);
beginShape();
vertex(70, 45);
vertex(120, 50);
vertex(170, 35);
vertex(180, 60);
vertex(165, 90);
vertex(120, 85);
vertex(80, 100);
vertex(65, 80);
vertex(60, 70);
endShape(CLOSE);
noStroke();
//teeth
fill(0,0,100);
//top-left
beginShape();
vertex(90, 50);
vertex(110, 52);
vertex(100, 65);
endShape(CLOSE);
//top-right
beginShape();
vertex(140, 47);
vertex(160, 41);
vertex(147, 71);
endShape(CLOSE);
//bottom-left
beginShape();
vertex(90, 92);
vertex(110, 85);
vertex(97, 74);
endShape(CLOSE);
//bottom-right
beginShape();
vertex(133, 84);
vertex(155, 86);
vertex(141, 74);
endShape(CLOSE);
pop();
updateScoreBoard('monster');
}
/**
* resets the scoreboard to its initial state
*/
function resetScoreBoard(){
//set up scoreboard
fill(0);
rect(0, 465, 200, 35);
fill(255);
textSize(40);
image(robotImg, 0, 465);
image(monsterImg, 100, 465);
text("00", 50, 465, 50, 50);
text("00", 150, 465, 50, 50);
}
/**
* updates the scoreboard eveytime a face is drawn
* @param {String} faceType - the face that has just been drawn
*/
function updateScoreBoard(faceType){
totalFaceCount++;
if(faceType == 'monster' && monsterCount < 99){
monsterCount++;
}
if(faceType == 'robot' && robotCount < 99){
robotCount++;
}
var monsterString = monsterCount;
var robotString = robotCount;
if(monsterCount < 10){
monsterString = '0' + monsterCount;
}
if(robotCount < 10){
robotString = '0' + robotCount;
}
fill(0);
rect(0, 465, 200, 35);
fill(255);
image(robotImg, 0, 465);
image(monsterImg, 100, 465);
textSize(40);
text(robotString, 50, 465, 50, 50);
text(monsterString, 150, 465, 50, 50);
//start the reset timer once canvas is full
if(totalFaceCount >= coOrdsArray.length && !timerIsRunning){
startResetTimer();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment