1// Copyright 2021 The Cirq Developers
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {
16  Vector3,
17  Line,
18  LineBasicMaterial,
19  BufferGeometry,
20  Texture,
21  SpriteMaterial,
22  Sprite,
23  Mesh,
24  MeshBasicMaterial,
25  SphereGeometry,
26  CylinderGeometry,
27  DoubleSide,
28  BoxGeometry,
29  Group,
30} from 'three';
31
32/**
33 * A wrapper for a three.js Line object representing a connection line
34 * between two qubits. Useful when building controlled gates.
35 */
36export class ConnectionLine extends Line {
37  /**
38   * Class constructor.
39   * @param startCoord The starting coordinate of the line
40   * @param endCoord The ending coordinate of the line
41   * @returns a CollectionLine object that can be added to a three.js scene
42   */
43  constructor(startCoord: Vector3, endCoord: Vector3) {
44    const COLOR = 'black';
45
46    const material = new LineBasicMaterial({color: COLOR});
47    const points = [startCoord, endCoord];
48    const geometry = new BufferGeometry().setFromPoints(points);
49
50    super(geometry, material);
51    return this;
52  }
53}
54
55/**
56 * A wrapper for a three.js Sprite object which is used to label the
57 * location of specific qubits.
58 */
59export class QubitLabel extends Sprite {
60  readonly text: string;
61  /**
62   * Class constructor.
63   * @param text The text which the label should display
64   * @returns a QubitLabel object that can be added to a three.js scene
65   */
66  constructor(text: string) {
67    const CANVAS_SIZE = 128;
68
69    const canvas = document.createElement('canvas');
70    canvas.width = CANVAS_SIZE;
71    canvas.height = CANVAS_SIZE;
72    // Allows us to keep track of what we're adding to the
73    // canvas.
74    canvas.textContent = text;
75
76    const context = canvas.getContext('2d')!;
77    context.fillStyle = '#000000';
78    context.textAlign = 'center';
79    context.font = '20px Arial';
80    context.fillText(text, CANVAS_SIZE / 2, CANVAS_SIZE / 2);
81
82    const map = new Texture(canvas);
83    map.needsUpdate = true;
84
85    const material = new SpriteMaterial({
86      map,
87      transparent: true, // for a transparent canvas background
88    });
89
90    super(material);
91    this.text = text;
92    return this;
93  }
94}
95
96/**
97 * A wrapper for a three.js Line object which represents a qubit in
98 * this circuit.
99 */
100export class QubitLine extends Line {
101  /**
102   * Class constructor.
103   * @param startCoord The starting coordinate of the line
104   * @param endCoord The ending coordinate of the line
105   * @returns a QubitLine object that can be added to a three.js scene.
106   */
107  constructor(startCoord: Vector3, endCoord: Vector3) {
108    const COLOR = 'gray';
109
110    const material = new LineBasicMaterial({color: COLOR});
111    const points = [startCoord, endCoord];
112    const geometry = new BufferGeometry().setFromPoints(points);
113
114    super(geometry, material);
115    return this;
116  }
117}
118
119/**
120 * A wrapper for a three.js Sphere which represents a control
121 * in a circuit.
122 */
123export class Control3DSymbol extends Mesh {
124  /**
125   * Class constructor.
126   * @returns a Control3DSymbol object that can be added to a three.js scene.
127   */
128  constructor() {
129    const COLOR = 'black';
130
131    const material = new MeshBasicMaterial({color: COLOR});
132    const geometry = new SphereGeometry(0.1, 32, 32);
133
134    super(geometry, material);
135    return this;
136  }
137}
138
139/**
140 * A wrapper for a three.js Group which represents an X operation
141 * in a circuit.
142 */
143export class X3DSymbol extends Group {
144  /**
145   * Class constructor.
146   * @param color The color of the symbol
147   * @returns an X3DSymbol object that can be added to a three.js scene
148   */
149  constructor(color: string) {
150    super();
151    const material = new MeshBasicMaterial({color: color, side: DoubleSide});
152    const geometry = new CylinderGeometry(
153      0.3,
154      0.3,
155      0.1,
156      32,
157      1,
158      true,
159      0,
160      2 * Math.PI
161    );
162    const hollowCylinder = new Mesh(geometry, material);
163    this.add(hollowCylinder);
164
165    // Creates the "X" in the middle of the holow cylinder
166    const rotationAngle = Math.PI / 2;
167
168    const xLineMaterial = new MeshBasicMaterial({color: color});
169    const xLineGeometry = new CylinderGeometry(0.01, 0.01, 0.6);
170    const xLine = new Mesh(xLineGeometry, xLineMaterial);
171    xLine.rotation.x = rotationAngle;
172
173    const zLineMaterial = new MeshBasicMaterial({color: color});
174    const zLineGeometry = new CylinderGeometry(0.01, 0.01, 0.6);
175    const zLine = new Mesh(zLineGeometry, zLineMaterial);
176    zLine.rotation.z = rotationAngle;
177
178    this.add(xLine);
179    this.add(zLine);
180    return this;
181  }
182}
183
184/**
185 * A wrapper for a three.js Group which represents an Swap operation
186 * in a circuit.
187 */
188export class Swap3DSymbol extends Group {
189  /**
190   * Class constructor.
191   * @returns a Swap3DSymbol object that can be added to a three.js scene
192   */
193  constructor() {
194    super();
195
196    const xLineMaterial = new MeshBasicMaterial({color: 'black'});
197    const xLineGeometry = new CylinderGeometry(0.01, 0.01, 0.3);
198    const xLine = new Mesh(xLineGeometry, xLineMaterial);
199    xLine.rotation.x = Math.PI / 2;
200    xLine.rotation.z = (3 * Math.PI) / 4;
201
202    const zLineMaterial = new MeshBasicMaterial({color: 'black'});
203    const zLineGeometry = new CylinderGeometry(0.01, 0.01, 0.3);
204    const zLine = new Mesh(zLineGeometry, zLineMaterial);
205    zLine.rotation.x = Math.PI / 2;
206    zLine.rotation.z = Math.PI / 4;
207
208    this.add(xLine);
209    this.add(zLine);
210    return this;
211  }
212}
213
214/**
215 * A wrapper for a three.js Box which represents arbitrary single qubit
216 * operations in a circuit
217 */
218export class BoxGate3DSymbol extends Mesh {
219  /**
220   * Class constructor.
221   * @param label The label that will go on the three.js box
222   * @param color The color of the box
223   * @returns a BoxGate3DSymbol object that can be added to a three.js scene
224   */
225  constructor(label: string, color: string) {
226    const canvas = document.createElement('canvas')!;
227    const context = canvas.getContext('2d')!;
228    canvas.width = canvas.height = 128;
229
230    context.fillStyle = color;
231    context.fillRect(0, 0, canvas.width, canvas.height);
232
233    let fontSize = 60;
234    let textWidth;
235    do {
236      fontSize /= 1.2;
237      context.font = `${fontSize}pt arial bold`;
238      textWidth = context.measureText(label).width;
239    } while (textWidth > canvas.width);
240
241    context.fillStyle = 'black';
242    context.fillText(
243      label,
244      canvas.width / 2 - textWidth / 2,
245      canvas.height / 2 + fontSize / 2
246    );
247
248    const map = new Texture(canvas);
249    map.needsUpdate = true;
250
251    const geometry = new BoxGeometry(0.5, 0.5, 0.5);
252    const material = new MeshBasicMaterial({map: map, color: color});
253
254    super(geometry, material);
255    return this;
256  }
257}
258