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 Scene, 17 PerspectiveCamera, 18 WebGLRenderer, 19 OrthographicCamera, 20 Vector3, 21 Box3, 22} from 'three'; 23import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'; 24import {GridCircuit} from './grid_circuit'; 25import {SymbolInformation} from './components/types'; 26 27class CircuitScene extends Scene { 28 private WIDTH = 1000; 29 private HEIGHT = 700; 30 31 public camera: PerspectiveCamera | OrthographicCamera; 32 public renderer: WebGLRenderer; 33 public controls: OrbitControls; 34 35 /** 36 * Support for both the Perspective and Orthographic 37 * cameras offered by three.js 38 */ 39 private perspectiveCamera: PerspectiveCamera; 40 private orthographicCamera: OrthographicCamera; 41 private perspectiveControls: OrbitControls; 42 private orthographicControls: OrbitControls; 43 44 /** 45 * Class constructor. 46 * Creates a three.js scene object, adds it to the container element 47 * at the designated id, and adds the orbit control functionality. 48 * @param sceneId The container id that will host the three.js scene object 49 * @returns The three.js scene object 50 */ 51 constructor(sceneId: string) { 52 super(); 53 54 this.renderer = new WebGLRenderer({alpha: true, antialias: true}); 55 this.renderer.setSize(this.WIDTH, this.HEIGHT); 56 57 this.perspectiveCamera = new PerspectiveCamera( 58 75, 59 this.WIDTH / this.HEIGHT, 60 0.1, 61 1000 62 ); 63 64 this.orthographicCamera = new OrthographicCamera( 65 this.WIDTH / this.HEIGHT / -2, 66 this.WIDTH / this.HEIGHT / 2, 67 this.HEIGHT / this.WIDTH / 2, 68 this.HEIGHT / this.WIDTH / -2, 69 0.1, 70 100 71 ); 72 this.orthographicCamera.zoom = 0.1; 73 // The default camera is the Perspective camera 74 this.camera = this.perspectiveCamera; 75 76 this.perspectiveControls = new OrbitControls( 77 this.perspectiveCamera, 78 this.renderer.domElement 79 ); 80 this.orthographicControls = new OrbitControls( 81 this.orthographicCamera, 82 this.renderer.domElement 83 ); 84 // The default controls are the Orbit controls for the Perspective camera 85 this.controls = this.perspectiveControls; 86 87 const el = document.getElementById(sceneId)!; 88 el.appendChild(this.renderer.domElement); 89 90 this.animate(); 91 } 92 93 /** 94 * Orients the camera and controls based off of the generated circuit, 95 * setting its position, zoom, and orientation. 96 * @param circuit The circuit that the camera is oriented off of. 97 */ 98 setCameraAndControls(circuit: GridCircuit) { 99 // Set the camera positions 100 this.fitPerspectiveCamera(circuit); 101 this.fitOrthographicCamera(circuit); 102 103 // Tells the camera which way is up. 104 this.camera.up.set(0, 1, 0); 105 106 // The camera will always look at circuit to start 107 this.camera.lookAt(circuit.position); 108 109 // Tells the control anchor to be set to the center 110 // of the circuit. 111 const center = new Box3().setFromObject(circuit).getCenter(new Vector3()); 112 this.controls.target.set(center.x, center.y, center.z); 113 } 114 115 /** 116 * Toggles between a Perspective and Orthographic camera, resetting 117 * the camera to the default orientation each time. 118 * @param circuit The circuit that the camera is oriented off of. 119 */ 120 toggleCamera(circuit: GridCircuit) { 121 if (this.camera instanceof PerspectiveCamera) { 122 this.camera = this.orthographicCamera; 123 this.controls = this.orthographicControls; 124 } else { 125 this.camera = this.perspectiveCamera; 126 this.controls = this.perspectiveControls; 127 } 128 129 this.setCameraAndControls(circuit); 130 } 131 132 private animate() { 133 requestAnimationFrame(this.animate.bind(this)); 134 this.controls.update(); 135 this.renderer.render(this, this.camera); 136 } 137 138 private fitPerspectiveCamera(circuit: GridCircuit) { 139 const boundingBox = new Box3(); 140 boundingBox.setFromObject(circuit); 141 142 const size = boundingBox.getSize(new Vector3()); 143 144 const max = Math.max(size.x, size.y, size.z); 145 const fov = this.perspectiveCamera.fov * (Math.PI / 180); 146 147 const z = Math.abs(max / Math.sin(fov / 2)); 148 149 this.perspectiveCamera.position.x = 0; 150 this.perspectiveCamera.position.y = 2.5; 151 this.perspectiveCamera.position.z = z; 152 } 153 154 private fitOrthographicCamera(circuit: GridCircuit) { 155 // TODO: Issue #4395. For more precision, calculate the bounding box of the 156 // circuit and set the orthographic camera to the four corners 157 // plus an offset. 158 this.orthographicCamera.position.x = 1; 159 this.orthographicCamera.position.y = 2; 160 this.orthographicCamera.zoom = 0.07; 161 this.orthographicCamera.updateProjectionMatrix(); 162 163 this.orthographicCamera.lookAt(circuit.position); 164 } 165} 166 167/** 168 * Creates and returns an empty GridCircuit object with qubits at the 169 * designated coordinates. The returned GridCircuit object can then take 170 * input to add gates to the circuit. 171 * @param qubits A list of GridCoord objects representing the location of 172 * each qubit. 173 * @param numMoments The number of total moments in the circuit 174 * @param sceneId The container id with the three.js scene that will render 175 * the three.js components 176 * @param padding_factor A number that represents how much the visualization 177 * should be scaled on the x,z coordinate plane. Default is 1. 178 * @returns A GridCircuit object 179 */ 180export function createGridCircuit( 181 symbol_info: SymbolInformation[], 182 numMoments: number, 183 sceneId: string, 184 padding_factor = 1 185): {circuit: GridCircuit; scene: CircuitScene} { 186 const scene = new CircuitScene(sceneId); 187 188 const circuit = new GridCircuit(numMoments, symbol_info, padding_factor); 189 scene.add(circuit); 190 scene.setCameraAndControls(circuit); 191 192 return {circuit, scene}; 193} 194