kerjos-Mocap
This is my prototype for a live motion-capture installation, in which you would see a mirror-version of yourself on a screen, with these elements mapped to your body. I think that there’s a lot more potential for even these grabbing and eating motions, but one of the aspects of this project that I like is the element of surprise with what you might pull down from above you. I also think the fig-leaf, as roughly rendered as it is, adds a level of humor that jives with the limits of motion-capture software.
Here’s a video if you want to see the full loop.
My idea for the motion and treatment I really conceived at once (See my sketch below!); it’s a basic pairing, and if I developed this project more, I think that the motion of grabbing would drive my future treatments.
Here’s some process documentation for making the fruit:
I start usually by drawing the approximate shape in Illustrator, to get an idea for the bezier curves:
Or I work in my sketchbook, with how I think the curves will work:
In a separate Processing document, I render the shape:
And then they’re brought into the program:
And here’s my code, from two separate files adapted from the templates that Golan gave us:
// Renders a BVH file with Processing v3.2.1 // Note: mouseX controls the camera. import java.util.Arrays; PBvh myBrekelBvh; float[][] lastHandsPos; float[][] lastFingersPos; float[] lastHeadPos; float[] lastSpine1Pos; float fastest = 0; float cutOffHeight = 350; float eatRadius = 100; float maxYSpeedForPick = 0.75; float minXZSpeedForPick = 1.0; float eatSpeed = 3.0; float minFingerHandDistance = 18.0; boolean[] hasFruit = new boolean[]{false,false}; String[] fruit = new String[]{"none","none"}; int counter = 0; //------------------------------------------------ void setup() { size( 1280, 720 ); // Load a BVH file recorded with a Kinect v2, made in Brekel Pro Body v2. //myBrekelBvh = new PBvh( loadStrings( "pickingFruit.bvh" ) ); myBrekelBvh = new PBvh( loadStrings( "allTogether.bvh" ) ); //listBones(); } //------------------------------------------------ void draw() { background(0, 0, 0); updateAndDrawBody(); // Update and render the BVH file. See code below. } //------------------------------------------------ void updateAndDrawBody() { pushMatrix(); translate(width/2, height/2+300); // position the body in space scale(-1, -1); // correct for the coordinate system orientation scale(1.5,1.5); drawCutOffLine(); if (!myBrekelBvh.parser.paused) { myBrekelBvh.update(millis()); // update the BVH playback } // myBrekelBvh.draw(); // one way to draw the BVH file (see PBvh.pde) myBrekelBvh.drawBones(); // a different way to draw the BVH file if (myBrekelBvh.parser.loopedThisFrame) { lastHandsPos = null; lastFingersPos = null; lastHeadPos = null; lastSpine1Pos = null; fruit = new String[]{"none","none"}; hasFruit = new boolean[]{false,false}; } checkForPick(); checkForEat(); myBrekelBvh.drawFruit(fruit); myBrekelBvh.drawLeaf(); popMatrix(); } //------------------------------------------------ void drawCutOffLine() { stroke(100); line(-width,cutOffHeight,width,cutOffHeight); } //------------------------------------------------ void listBones() { List<BvhBone> theBvhBones = myBrekelBvh.parser.getBones(); int nBones = theBvhBones.size(); // How many bones are there? for (int i=0; i<nBones; i++) { // Loop over all the bones BvhBone aBone = theBvhBones.get(i); // Get the i'th bone println(aBone.getName()); } } //------------------------------------------------ void checkForPick() { float[][] handsPos; handsPos = myBrekelBvh.getHandsPos(); if (lastHandsPos == null) { lastHandsPos = handsPos; } float[][][] fingersPos; fingersPos = myBrekelBvh.getFingersPos(); lastHandsPos = handsPos; if (handsPos[0][1] > cutOffHeight) { if ((fingersAreNearHand(handsPos[0],fingersPos[0])) && (!hasFruit[0])) { println("PICKED LEFT"); fruit = new String[]{getRandomFruit(),fruit[1]}; hasFruit = new boolean[]{true,hasFruit[1]}; counter += 1; } } if (handsPos[1][1] > cutOffHeight) { if ((fingersAreNearHand(handsPos[1],fingersPos[1])) && (!hasFruit[1])) { println("PICKED RIGHT"); fruit = new String[]{fruit[0],getRandomFruit()}; hasFruit = new boolean[]{hasFruit[0],true}; counter += 1; } } } boolean fingersAreNearHand(float[] handPos, float[][] fingersPos) { boolean near = false; for (int i=0; i<5; i++) { float d = distance(handPos,fingersPos[i]); if (d < minFingerHandDistance) { near = true; } } return near; } float distance(float[] a, float[] b) { return sqrt(sq(a[0]-b[0])+sq(a[1]-b[1])+sq(a[2]-b[2])); } void checkForEat() { float[] headPos; headPos = myBrekelBvh.getHeadPos(); if (lastHeadPos == null) { lastHeadPos = headPos; } float [] spine1Pos; spine1Pos = myBrekelBvh.getSpine1Pos(); if (lastSpine1Pos == null) { lastSpine1Pos = spine1Pos; } float[][] handsPos; handsPos = myBrekelBvh.getHandsPos(); if (lastHandsPos == null) { lastHandsPos = handsPos; } float headSpeed = calcHeadSpeed(lastHeadPos,headPos); float spine1Speed = calcSpine1Speed(lastSpine1Pos,spine1Pos); lastHeadPos = headPos; lastSpine1Pos = spine1Pos; fill(0,0,255); //ellipse(headPos[0],headPos[1],10,10); noFill(); stroke(100); //ellipse(headPos[0],headPos[1],2*eatRadius,2*eatRadius); //println("headPos: " + headPos + " spine1Pos: " + spine1Pos); float[] d = calcHeadHandsDistance(headPos,handsPos); if ((d[0] < eatRadius)) { if ((headSpeed > eatSpeed) && (spine1Speed < eatSpeed) && (hasFruit[0])) { println("EATEN LEFT"); fruit = new String[]{"none",fruit[1]}; hasFruit = new boolean[]{false,hasFruit[1]}; } } if ((d[1] < eatRadius)) { if ((headSpeed > eatSpeed) && (spine1Speed < eatSpeed) && (hasFruit[1])) { println("EATEN RIGHT"); fruit = new String[]{fruit[0],"none"}; hasFruit = new boolean[]{hasFruit[0],false}; } } } float[] calcHandsXZVelocity(float[][] lastHandsPos, float[][] currHandsPos) { //Calculates velocity in x-z dimension for both hands. float[] xzVel = new float[2]; for (int i=0; i<2; i++) { float lastX = lastHandsPos[i][0]; float lastZ = lastHandsPos[i][2]; float currX = currHandsPos[i][0]; float currZ = currHandsPos[i][2]; float d = sqrt(sq(lastX-currX)+sq(lastZ-currZ)); xzVel[i] = d; } return xzVel; // [left v, right v] } float[] calcHandsYVelocity(float[][] lastHandsPos, float[][] currHandsPos) { float[] yVel = new float[2]; for (int i=0; i<2; i++) { float lastY = lastHandsPos[i][1]; float currY = currHandsPos[i][1]; float dy = abs(lastY - currY); yVel[i] = dy; } return yVel; } float calcHeadSpeed(float[] lastHeadPos, float[] currHeadPos) { float lastX = lastHeadPos[0]; float lastY = lastHeadPos[1]; float lastZ = lastHeadPos[2]; float currX = currHeadPos[0]; float currY = currHeadPos[1]; float currZ = currHeadPos[2]; float s = sqrt(sq(lastX-currX)+sq(lastY-currY)+sq(lastZ-currZ)); return s; } float calcSpine1Speed(float[] lastSpine1Pos, float[] currSpine1Pos) { float lastX = lastSpine1Pos[0]; float lastY = lastSpine1Pos[1]; float lastZ = lastSpine1Pos[2]; float currX = currSpine1Pos[0]; float currY = currSpine1Pos[1]; float currZ = currSpine1Pos[2]; float s = sqrt(sq(lastX-currX)+sq(lastY-currY)+sq(lastZ-currZ)); return s; } float[] calcHeadHandsDistance(float[] headPos, float[][] handsPos) { float[] d = new float[2]; float headX = headPos[0]; float headY = headPos[1]; float headZ = headPos[2]; for (int i=0; i<2; i++) { float handX = handsPos[i][0]; float handY = handsPos[i][1]; float handZ = handsPos[i][2]; d[i] = sqrt(sq(headX - handX) + sq(headY - handY) + sq(headZ - handZ)); } return d; } String getRandomFruit() { String[] possibles = new String[]{"apple","orange","banana"}; int rand = floor(random(0,3)); /*if (counter == 0) { return "orange"; } else if (counter == 1) { return "banana"; } else if (counter == 2) { return "snake"; } else if (counter == 3) { return "apple"; } if (random(0,1) > 0.8) { return "snake"; }*/ return possibles[rand]; } //------------------------------------------------ void mousePressed() { myBrekelBvh.parser.paused = !myBrekelBvh.parser.paused; } |
import java.util.List; class PBvh { BvhParser parser; PBvh(String[] data) { parser = new BvhParser(); parser.init(); parser.parse( data ); } void update( int ms ) { parser.moveMsTo( ms );//30-sec loop parser.update(); } //------------------------------------------------ void draw() { // Previous method of drawing, provided by Rhizomatiks/Perfume fill(color(255)); for ( BvhBone b : parser.getBones()) { pushMatrix(); translate(b.absPos.x, b.absPos.y, b.absPos.z); ellipse(0, 0, 2, 2); popMatrix(); if (!b.hasChildren()) { pushMatrix(); translate( b.absEndPos.x, b.absEndPos.y, b.absEndPos.z); ellipse(0, 0, 10, 10); popMatrix(); } } } //------------------------------------------------ // Alternate method of drawing, added by Golan void drawBones() { noFill(); stroke(255); strokeWeight(1.0); if (parser.loopedThisFrame) { println("looped"); } List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); // How many bones are there? for (int i=0; i<nBones; i++) { // Loop over all the bones BvhBone aBone = theBvhBones.get(i); // Get the i'th bone PVector boneCoord0 = aBone.absPos; // Get its start point float x0 = boneCoord0.x; // Get the (x,y,z) values float y0 = boneCoord0.y; // of its start point //float z0 = boneCoord0.z; if (aBone.hasChildren()) { // If this bone has children, // draw a line from this bone to each of its children List<BvhBone> childBvhBones = aBone.getChildren(); int nChildren = childBvhBones.size(); for (int j=0; j<nChildren; j++) { BvhBone aChildBone = childBvhBones.get(j); PVector boneCoord1 = aChildBone.absPos; float x1 = boneCoord1.x; float y1 = boneCoord1.y; //float z1 = boneCoord1.z; stroke(255); line(x0, y0, x1, y1); } } else { // Otherwise, if this bone has no children (it's a terminus) // then draw it differently. PVector boneCoord1 = aBone.absEndPos; // Get its start point float x1 = boneCoord1.x; float y1 = boneCoord1.y; //float z1 = boneCoord1.z; line(x0, y0, x1, y1); String boneName = aBone.getName(); if (boneName.equals("Head")) { pushMatrix(); translate( x1, y1); noFill(); //ellipse(0, 0, 30, 30); popMatrix(); } } } } //------------------------------------------------ // kerjos void drawFruit(String[] fruit) { for (int i=0; i<2; i++) { if (fruit[i] == "none") { continue; } else { //There is a fruit in this hand. float[][] handCenters; handCenters = getHandsCenterPos(); //[[lx,ly,lz],[rx,ry,rz]] float x = handCenters[i][0]; float y = handCenters[i][1]; if (fruit[i] == "apple") { pushMatrix(); translate(x,y); scale(0.05,0.05); rotate(PI); drawApple(); popMatrix(); } else if (fruit[i] == "orange") { stroke(0); strokeWeight(1); fill(255,204,0); ellipse(x,y,25,25); } else if (fruit[i] == "banana") { pushMatrix(); translate(x,y); scale(0.12,0.12); rotate(PI/2); drawBanana(); popMatrix(); } else if (fruit[i] == "snake") { pushMatrix(); translate(x,y); scale(0.2,0.2); rotate(PI); drawSnake(); popMatrix(); } } } } void drawSnake() { noFill(); stroke(0); strokeWeight(3); float h = 200; float t = 75; beginShape(); fill(0, 102, 0); vertex(0,-h+t/2); bezierVertex(-t/2,-h+t/2, -t,-h+t/4, -t,-h); vertex(0,-h-t/2); bezierVertex(t/4,-h-t/2, h/2+t/2,-h/2-t/4, h/2+t/2,-h/2); bezierVertex(h/2+t/2,-h/2+t/4, t/2,-t/2, t/2,0); bezierVertex(t/2,t/2, h/2,h/2-t/4, h/2,h/2); bezierVertex(h/2,h/2+t/2, t/2,h, 0,h); bezierVertex(t/2,h-t/2, h/2-t/2,h/2+t/2, h/2-t/2,h/2); bezierVertex(h/2-t/2,h/2-t/2, -t/2,t/2, -t/2,0); bezierVertex(-t/2,-t/2, h/2-t/2,-h/2+t/2, h/2-t/2,-h/2); bezierVertex(h/2-t/2,-h/2-t/2, t/2,-h+t/2, 0,-h+t/2); endShape(); fill(204, 0, 0); ellipse(0,-h,20,20); } void drawApple() { float r = 100; //Stem float w = r/4; float h = r*1.5; stroke(0); strokeWeight(3); fill(102,51,0); pushMatrix(); translate(0,-r*1.25); beginShape(); vertex(w/2,0); bezierVertex(w/2,-h/2, w,-h, w*1.5,-h); vertex(-w/2,-h-w); bezierVertex(-w,-h, -w,-h/2, -w/2,0); endShape(); popMatrix(); //Red Fruit stroke(0); strokeWeight(3); fill(204,0,0); beginShape(); //Top Left vertex(0,-r*1.25); bezierVertex(0,-r*1.5, -r*0.5,-r*2, -r,-r*2); bezierVertex(-r*1.5,-r*2, -r*2,-r*1.75, -r*2,-r); //Left Side bezierVertex(-r*2,r*0.5, -r*1.5,r*2, -r,r*2); //Bottom bezierVertex(-r*0.5,r*2, -r*0.5,r*1.5, 0,r*1.5); bezierVertex(r*0.5,r*1.5, r*0.5,r*2, r,r*2); //Right Side bezierVertex(r*1.5,r*2, r*2,r*0.5, r*2,-r); //Top Right bezierVertex(r*2,-r*1.75, r*1.5,-r*2, r,-r*2); bezierVertex(r*0.5,-r*2, 0,-r*1.5, 0,-r*1.25); endShape(); } void drawBanana() { noFill(); stroke(255,0,0); float w = 250; line(0,0,w/2,0); line(0,0,-w/2,0); //Stem float sw = w/25; float h = w*0.1; stroke(0); strokeWeight(3); fill(102,51,0); pushMatrix(); translate(w/2+w/10,0); rotate(PI/4); beginShape(); vertex(-sw/2,0); bezierVertex(-sw/2,-h/2, -sw,-h, -sw*1.5,-h); vertex(sw/2,-h-sw); bezierVertex(sw,-h, sw,-h/2, sw/2,0); endShape(); popMatrix(); //Yellow Fruit float tip = w/10; float side = w/5; float inside = w/3; stroke(0); strokeWeight(3); fill(255,255,0); beginShape(); vertex(w/2,0); float nextX = w/2+(w/8)*cos(-PI/4); float nextY = 0-(w/8)*sin(-PI/4); bezierVertex(w/2+tip*cos(PI/6),0-tip*sin(PI/6), nextX+side*cos(PI/3),nextY-side*sin(PI/3), nextX,nextY); nextX = -w/2-(w/8)*cos(-PI/4); nextY = 0-(w/8)*sin(-PI/4); bezierVertex(0,2*w/3, nextX+side*cos(PI/4),nextY+side*sin(PI/4), nextX,nextY); float prevX = nextX; float prevY = nextY; bezierVertex(prevX-side*cos(PI/3),prevY-side*sin(PI/3), -w/2-tip*cos(PI/6),0-tip*sin(PI/6), -w/2,0); bezierVertex(-w/2+side*cos(PI/6),0+side*sin(PI/6), w/2-side*cos(PI/6),0+side*sin(PI/6), w/2,0); endShape(); } void drawLeaf() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord0 = aBone.absPos; float x0 = boneCoord0.x; float y0 = boneCoord0.y; if (aBone.getName().equals("Hips")) { pushMatrix(); translate(x0,y0); scale(0.1,0.1); rotate(PI); drawFigLeaf(); popMatrix(); } } } void drawFigLeaf() { noFill(); stroke(255,0,0); float s = 200; pushMatrix(); translate(0,-s/2); //Stem drawStem(s); //Center leaf tip drawLeafTip(s); //Right leaf tip pushMatrix(); rotate(PI/4); drawLeafTip(s); popMatrix(); //Left leaf tip pushMatrix(); rotate(-PI/4); drawLeafTip(s); popMatrix(); //Leaf body noStroke(); fill(0, 204, 0); //line(-s/4,0,s/4,0); //stroke(0); beginShape(); vertex(-s/8,s/2); bezierVertex(-s/16,s/2-s/16, -s/4,s/2-s/8, -s/4-s/64,s/2-s/16); vertex(-s/2-s/8,s/4+s/32); //bezierVertex(-s/2+3*s/16,s/4-s/16, -s/4,s/16, -s/4,0); //bezierVertex(-s/4,-s/16, 3*-s/16,-s/8, -s/8,-s/8); bezierVertex(-s/2-s/16,s/4-s/16, 3*-s/8,-s/8, -s/8,-s/8); bezierVertex(-s/16,-s/8, 0,-s/16, 0,0); bezierVertex(0,-s/16, s/16,-s/8, s/8,-s/8); bezierVertex(3*s/8,-s/8, s/2+s/16,s/4-s/16, s/2+s/8,s/4+s/32); //bezierVertex(3*s/16,-s/8, s/4,-s/16, s/4,0); //bezierVertex(s/4,s/16, s/2-3*s/16,s/4-s/16, s/2+s/8,s/4+s/32); vertex(s/4+s/64,s/2-s/16); bezierVertex(s/4,s/2-s/8, s/16,s/2-s/16, s/8,s/2); endShape(); popMatrix(); } void drawLeafTip(float s) { stroke(255); noFill(); line(0,0,0,s); line(0,s/2,s/8,s/2); line(0,s/2,-s/8,s/2); line(0,2*s/3,s/4,2*s/3); line(0,2*s/3,-s/4,2*s/3); line(0,15*s/16,s/16,15*s/16); line(0,15*s/16,-s/16,15*s/16); //stroke(0); fill(0, 204, 0); beginShape(); vertex(-s/8,s/2); bezierVertex(-s/8-s/16,s/2+s/16, -s/4,2*s/3-s/16, -s/4,2*s/3); vertex(-s/16,15*s/16); bezierVertex(-s/32,s, s/32,s, s/16,15*s/16); vertex(s/4,2*s/3); bezierVertex(s/4,2*s/3-s/16, s/8+s/16,s/2+s/16, s/8,s/2); endShape(); } void drawStem(float s) { //Stem float w = s/16; float h = s/3; noStroke(); //stroke(0); //strokeWeight(3); fill(102,51,0); beginShape(); vertex(w/2,0); bezierVertex(w/2,-h/2, w,-h, w*1.5,-h); vertex(-w/2,-h-w); bezierVertex(-w,-h, -w,-h/2, -w/2,0); endShape(); } //------------------------------------------------ float[][] getHandsPos() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); float[][] handsPos = new float[2][3]; for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord = aBone.absPos; float x = boneCoord.x; float y = boneCoord.y; float z = boneCoord.z; if (aBone.getName().equals("LeftHand")) { handsPos[0][0] = x; handsPos[0][1] = y; handsPos[0][2] = z; fill(255,0,0); //ellipse(x,y,10,10); } else if (aBone.getName().equals("RightHand")) { handsPos[1][0] = x; handsPos[1][1] = y; handsPos[1][2] = z; fill(0,255,0); //ellipse(x,y,10,10); } } return handsPos; } //------------------------------------------------ float[][] getHandsCenterPos() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); float[][] handsPos = new float[2][3]; for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord = aBone.absPos; float x = boneCoord.x; float y = boneCoord.y; float z = boneCoord.z; if (aBone.getName().equals("LeftHandMiddle1")) { handsPos[0][0] = x; handsPos[0][1] = y; handsPos[0][2] = z; } else if (aBone.getName().equals("RightHandMiddle1")) { handsPos[1][0] = x; handsPos[1][1] = y; handsPos[1][2] = z; } } return handsPos; } //------------------------------------------------ float[][][] getFingersPos() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); float[][][] fingersPos = new float[2][5][3]; for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord = aBone.absPos; float x = boneCoord.x; float y = boneCoord.y; float z = boneCoord.z; fill(255,0,0); if (aBone.getName().equals("LeftHandIndex3")) { fingersPos[0][1][0] = x; fingersPos[0][1][1] = y; fingersPos[0][1][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("LeftHandMiddle3")) { fingersPos[0][2][0] = x; fingersPos[0][2][1] = y; fingersPos[0][2][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("LeftHandRing3")) { fingersPos[0][3][0] = x; fingersPos[0][3][1] = y; fingersPos[0][3][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("LeftHandPinky3")) { fingersPos[0][4][0] = x; fingersPos[0][4][1] = y; fingersPos[0][4][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("LeftHandThumb3")) { fingersPos[0][0][0] = x; fingersPos[0][0][1] = y; fingersPos[0][0][2] = z; //ellipse(x,y,3,3); } fill(0,255,0); if (aBone.getName().equals("RightHandIndex3")) { fingersPos[1][1][0] = x; fingersPos[1][1][1] = y; fingersPos[1][1][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("RightHandMiddle3")) { fingersPos[1][2][0] = x; fingersPos[1][2][1] = y; fingersPos[1][2][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("RightHandRing3")) { fingersPos[1][3][0] = x; fingersPos[1][3][1] = y; fingersPos[1][3][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("RightHandPinky3")) { fingersPos[1][4][0] = x; fingersPos[1][4][1] = y; fingersPos[1][4][2] = z; //ellipse(x,y,3,3); } else if (aBone.getName().equals("RightHandThumb3")) { fingersPos[1][0][0] = x; fingersPos[1][0][1] = y; fingersPos[1][0][2] = z; //ellipse(x,y,3,3); } } return fingersPos; } //------------------------------------------------ float[] getHeadPos() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); float[] headPos = new float[3]; for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord = aBone.absEndPos; float x = boneCoord.x; float y = boneCoord.y; float z = boneCoord.z; if (aBone.getName().equals("Head")) { headPos[0] = x; headPos[1] = y; headPos[2] = z; } } return headPos; } //------------------------------------------------ float[] getSpine1Pos() { List<BvhBone> theBvhBones = parser.getBones(); int nBones = theBvhBones.size(); float[] spine1Pos = new float[3]; for (int i=0; i<nBones; i++) { BvhBone aBone = theBvhBones.get(i); PVector boneCoord = aBone.absPos; float x = boneCoord.x; float y = boneCoord.y; float z = boneCoord.z; if (aBone.getName().equals("Spine1")) { spine1Pos[0] = x; spine1Pos[1] = y; spine1Pos[2] = z; } } return spine1Pos; } } |