From 1478e7a03d5479018f6008efc3f8c2e918536e80 Mon Sep 17 00:00:00 2001 From: PabloAsensio Date: Thu, 19 Oct 2023 15:26:59 +0200 Subject: [PATCH 1/3] merge squash from exercise/visual_odometry_3D/dev --- db.sqlite3 | Bin 327680 -> 327680 bytes .../assets/img/visual_odometry_3D_teaser.png | Bin 0 -> 207459 bytes .../dataset/sequences/01/image_0/000000.png | Bin 0 -> 259051 bytes .../exercises/visual_odometry_3D/README.md | 1 + .../visual_odometry_3D/code/academy.py | 7 + .../exercises/visual_odometry_3D/console.py | 18 + .../exercises/visual_odometry_3D/exercise.py | 385 + .../exercises/visual_odometry_3D/gui.py | 197 + .../exercises/visual_odometry_3D/hal.py | 209 + .../visual_odometry_3D/interfaces/__init__.py | 0 .../visual_odometry_3D/interfaces/camera.py | 203 + .../interfaces/camera_parameters.py | 164 + .../interfaces/pinhole_camera.py | 93 + .../interfaces/threadPublisher.py | 46 + .../interfaces/visual_odometry.py | 153 + .../launch/kobuki_1_reconstruccion3d.world | 122 + .../launch/visual_odometry_3D_ros.launch | 22 + .../visual_odometry_3D_conf.yml | 49 + .../visual_odometry_3D/exercise.html | 453 + instructions.json | 165 + manager/manager.py | 6 +- .../visual_odometry_3D/3DScene/3DViz.js | 34 + .../visual_odometry_3D/3DScene/3d_scene.js | 211 + .../3DScene/ColladaLoader.js | 3627 ++ .../visual_odometry_3D/3DScene/OBJLoader.js | 793 + .../3DScene/OrbitControls.js | 1055 + .../visual_odometry_3D/3DScene/pose3d.js | 33 + .../visual_odometry_3D/3DScene/three.js | 45930 ++++++++++++++++ .../visual_odometry_3D/js/autocorrector.js | 20 + .../visual_odometry_3D/js/controller.js | 40 + .../visual_odometry_3D/js/ws_code.js | 110 + .../exercises/visual_odometry_3D/js/ws_gui.js | 125 + .../visual_odometry_3D/utils/CBuffer.js | 31 + .../visual_odometry_3D/vehicles/car.js | 37 + .../visual_odometry_3D/vehicles/frame.js | 50 + 35 files changed, 54386 insertions(+), 3 deletions(-) create mode 100644 exercises/static/exercises/assets/img/visual_odometry_3D_teaser.png create mode 100644 exercises/static/exercises/assets/kitti/dataset/sequences/01/image_0/000000.png create mode 100644 exercises/static/exercises/visual_odometry_3D/README.md create mode 100644 exercises/static/exercises/visual_odometry_3D/code/academy.py create mode 100644 exercises/static/exercises/visual_odometry_3D/console.py create mode 100644 exercises/static/exercises/visual_odometry_3D/exercise.py create mode 100644 exercises/static/exercises/visual_odometry_3D/gui.py create mode 100644 exercises/static/exercises/visual_odometry_3D/hal.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/__init__.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/camera.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/camera_parameters.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/threadPublisher.py create mode 100644 exercises/static/exercises/visual_odometry_3D/interfaces/visual_odometry.py create mode 100644 exercises/static/exercises/visual_odometry_3D/launch/kobuki_1_reconstruccion3d.world create mode 100644 exercises/static/exercises/visual_odometry_3D/launch/visual_odometry_3D_ros.launch create mode 100644 exercises/static/exercises/visual_odometry_3D/visual_odometry_3D_conf.yml create mode 100644 exercises/templates/exercises/visual_odometry_3D/exercise.html create mode 100644 instructions.json create mode 100644 static/exercises/visual_odometry_3D/3DScene/3DViz.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/3d_scene.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/ColladaLoader.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/OBJLoader.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/OrbitControls.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/pose3d.js create mode 100644 static/exercises/visual_odometry_3D/3DScene/three.js create mode 100644 static/exercises/visual_odometry_3D/js/autocorrector.js create mode 100644 static/exercises/visual_odometry_3D/js/controller.js create mode 100644 static/exercises/visual_odometry_3D/js/ws_code.js create mode 100644 static/exercises/visual_odometry_3D/js/ws_gui.js create mode 100644 static/exercises/visual_odometry_3D/utils/CBuffer.js create mode 100644 static/exercises/visual_odometry_3D/vehicles/car.js create mode 100644 static/exercises/visual_odometry_3D/vehicles/frame.js diff --git a/db.sqlite3 b/db.sqlite3 index 424d6f9aba80adb50341572263ef7e7c37487e4b..fcd0dbb1421fab5cd4c179f5a27f144a8e82e71f 100644 GIT binary patch delta 1380 zcmah|OKcle6!m-0GZVXZNu-99CTbj~s3_pcy!m-NT~MngF;0w|hD6X0)g+$AcI&Zg zJ9b^i@h1ogu^?5DW=S?6*i;2e3?Lz~VFSvd3)F%F3R^(Hijqo2jK>94*itw5N^kDD z@60*x)a!@p^+TJ(;O>o)Q^Z{x9jks6-={%i>$S-tFx4tfMotc5#)l;j7GzeE8J1=l zMv!pp+sq_`qV~NlYWp}Oh%I`10^0XluWY|=j~qi$kd8COR9VcjiA+8zZ&2Id z9(0i-Xp_x}Dr1Z??uf zE>i#XPvGRSkHL?sg#}TJ9z{XGGA!pa#pKM-tjzN=E7Acmz;oiPVImKIhDO{Tow@}j z;4$~@xrUxZ7!;HFRprXXOgxiT3%T+|E;MEMzkoZ?oc>&=zJz|@)|Hd+)fVINx-H0} z1@XilLOgBA?H$OmBQ6-S9o%()>U!ru);a4q-UrED;uHHOO2LpR=P~$9b395!Hx`1A zvrkus;h?@_NAK1O(tJifeYzYwH8aP&bY?n{T6}r&#dJBTE(9-ywfSs1SY$5cmNJ=$ zfTx$1J391|sX0DtUH-UdL93B#i>XjPR!RlubZr>+Sxh4N)Lcmx&P=IU(H~dl!m(+# zxRP5*;i9C>7vpEsXS4BAZskfS{DR+KRvt3x<%qFqM}=R|VG@}hqo-Yc1Jo8ZPJT!R zNo0ES4N*I>0UWmA03HC2|D5f_gp*_r(-P(+N%Aw@W}Le~L3P7sq1A4*C}V+Ud6pCS zp6H5qIiYBJ;sF{9UZ_^hWu<1#*{0HAOmuSpNf?w>lO;;YE=jC*B-D0!ag=YgY z$Iy)6XPKV=qK{SJs9s~y(1}X3+F|j;|F-b5Ajp`fS&{dPy;spP^Tu`~mjs{Xas7 Tam0sqplp^UaIcLiKlK=^C&%fFGrDdr)Aw=^HL%n*vQ#jzurf5a zGP2Y&H#IXfve?H~GO5md$!gul-`KW>enI zzJT#N)8<40Rp#x=%NQSWGFP+rZ)e}Yc#~=Sn@x=MARPwV`?oWeS}=RFy_wGbfpPj~ zMFl^$>Fd5Ra&7yboLEQ7K|H#Qm=S;1sIUPcJsYVXPJO%E>34Zz+}$E zCl6HAz$++%qz25~e(@}mv5^E9%LE3#JG?u1rf?T<`EaUpFtOcX-NrHj=*AM3?Gsp- zOI|Yzal`;!!mJ83$$(>f%n#-j-?^B$%oup(xXd{B07d24nOMI7bxdSpteTuCP`2Id I7t1MD0HR)GX8-^I diff --git a/exercises/static/exercises/assets/img/visual_odometry_3D_teaser.png b/exercises/static/exercises/assets/img/visual_odometry_3D_teaser.png new file mode 100644 index 0000000000000000000000000000000000000000..a0981676b39007a461391ca390eb92f0cefb5d0e GIT binary patch literal 207459 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%0DTF@DU$iF?)ssEo3+AK!9`oQN_4nE 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%0DQJ6R&YEp@=H8Opl5_2D$DK+wG<3s~ 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%0D|M$oT3RtHsQ89MZItwOyAaYBrx1=XZ@YFExR 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�hA&1VjaED+i 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 Ybl{N!3K5mI%=jnmp5H}Irf*~a0h=b5DF6Tf literal 0 HcmV?d00001 diff --git a/exercises/static/exercises/assets/kitti/dataset/sequences/01/image_0/000000.png b/exercises/static/exercises/assets/kitti/dataset/sequences/01/image_0/000000.png new file mode 100644 index 0000000000000000000000000000000000000000..d677a306a7f26d9538152fa38b7e9628e349015e GIT binary patch literal 259051 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⩔?PnPW}a{cp{Q-AHbx7J4f<+r8rFx5 zHE(!xUlzPaOtQPJkP|;ng|&|g@zMlqY4tEZk-v~eyZ`V(-bUo#sxHxG=SraUsM=h-?msZ*8w*9CHg*7a4BN--66kKZ~TL~Uc zP*uWrMM$QKp5or zhL0wR$(5uOVVQC~j=yWmmp0xo+DO-;1pFxDxmLDw$&KuKb&Req82SRE1<=dw*rS1@ zW5GuOz{kh;YFtdK@Cd8*M|(m%kA*sb zkFyc5wZ%fi2fB^ZgdaUY$nz{^?6t2YIvD9=>lhv3E}6~4Nl33MBPguGI_9N+ZM`8t zub?~@X;8*v#YN_%R?MADw|R6vql1bb5TAKz+tBDHKH@WaleR(fa;J`8aoY_G?TK`q zjckeJH>YtpPMXR6zulf35gFUwvaLTG^)O*jA!-&BCJ84yOA1B}w3~uTev{5jTNfwD zThw_Qqm{j5^6^QSfy9iHhqG>6qhO}9#v7wJI?7KXI90A_&Kesz)iYAm%kZ+5_o^qY z<||~j&EbiCAG(=1)cqB>&bgQw7wa-77LZ0H3F!D+@w3ThW@Aw`MJ(b#z*)<)1l6d( zHc>J#JHF~|R>UnjqBB=XAlUlTR2`abrxM{2s+slmrC($?BtWb_E;X(c{3KhEiT|=T zsDV>L5HWG5wGS(QYpqlzPB(T*?Y91zm}pw-0Ty^0?6k^gED(CLx3-RPl$f}#+r~3T zBcER*8szL9eNhbtpK?t%)bf#~cMMf}rtUduvG(`i`n+H9K4E#6kDUaRIY6#L8XoO9 zmuaKhv9}(t;SqE~!#?L9cRhfBt$Vae@(yNeydFYh0iAnO4YvX#BIBx0_OBhWgP7|> z3|+$M)C`_2>wvM7N0GU%z+$)gJm3|`o4*P(-5SGdEkQ%NTyMZ>%IO^MHuB(*d0Bb$%QIsX zPxZrO<-*#kGG$ymQb|s!vu(L2b(GNlHdhy$({H77{_8@uZPV%u-Mh{ej$~%f;{21O zJyfexAI-v5TogiHL}We@T®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ɖN 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 S!G0$I0000= 0): + iterative_code = '\n' + iterative_code + extra_lines -= 1 + iterative_code = re.sub(r'^[ ]{4}', '', iterative_code, flags=re.M) + + except: + sequential_code = source_code + iterative_code = "" + + return sequential_code, iterative_code + + # The process function + def process_code(self, source_code): + # Redirect the information to console + start_console() + + # Reference Environment for the exec() function + iterative_code, sequential_code = self.parse_code(source_code) + + # print("The debug level is " + str(debug_level) + # print(sequential_code) + # print(iterative_code) + + # The Python exec function + # Run the sequential part + gui_module, hal_module = self.generate_modules() + reference_environment = {"GUI": gui_module, "HAL": hal_module} + exec(sequential_code, reference_environment) + + # Run the iterative part inside template + # and keep the check for flag + while self.reload == False: + while (self.stop_brain == True): + if (self.reload == True): + break + time.sleep(0.1) + + start_time = datetime.now() + + # Execute the iterative portion + exec(iterative_code, reference_environment) + + # Template specifics to run! + finish_time = datetime.now() + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + + # Keep updating the iteration counter + if (iterative_code == ""): + self.iteration_counter = 0 + else: + self.iteration_counter = self.iteration_counter + 1 + + # The code should be run for atleast the target time step + # If it's less put to sleep + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) + + close_console() + print("Current Thread Joined!") + + # Function to generate the modules for use in ACE Editor + def generate_modules(self): + # Define HAL module + hal_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + hal_module.HAL = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("HAL", None)) + + # Add HAL functions + hal_module.HAL.advance = self.hal.advance + hal_module.HAL.get_current_groundtruth_position = self.hal.get_current_groundtruth_position + hal_module.HAL.get_image = self.hal.get_image + + hal_module.HAL.set_estimated_position = self.hal.set_estimated_position + hal_module.HAL.set_estimated_euler_angles = self.hal.set_estimated_euler_angles + + hal_module.HAL.get_true_euler_angles_corrected_array = self.hal.get_true_euler_angles_corrected_array # A BORRAR + + # Define GUI module + gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + gui_module.GUI = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) + + # Add GUI functions + gui_module.GUI.show_image = self.gui.show_image + + # Adding modules to system + # Protip: The names should be different from + # other modules, otherwise some errors + sys.modules["HAL"] = hal_module + sys.modules["GUI"] = gui_module + + return gui_module, hal_module + + # Function to measure the frequency of iterations + def measure_frequency(self): + previous_time = datetime.now() + # An infinite loop + while True: + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from the previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + # Send to client + self.send_frequency_message() + + # Function to generate and send frequency messages + def send_frequency_message(self): + # This function generates and sends frequency measures of the brain and gui + brain_frequency = 0 + gui_frequency = 0 + try: + brain_frequency = round(1000 / self.measured_cycle, 1) + except ZeroDivisionError: + brain_frequency = 0 + + try: + gui_frequency = round(1000 / self.thread_gui.measured_cycle, 1) + except ZeroDivisionError: + gui_frequency = 0 + + self.frequency_message["brain"] = brain_frequency + self.frequency_message["gui"] = gui_frequency + self.frequency_message["rtf"] = self.real_time_factor + + message = "#freq" + json.dumps(self.frequency_message) + self.server.send_message(self.client, message) + + def send_ping_message(self): + self.server.send_message(self.client, "#ping") + + # Function to notify the front end that the code was received and sent to execution + def send_code_message(self): + self.server.send_message(self.client, "#exec") + + # Function to track the real time factor from Gazebo statistics + # https://stackoverflow.com/a/17698359 + # (For reference, Python3 solution specified in the same answer) + def track_stats(self): + args = ["gz", "stats", "-p"] + # Prints gz statistics. "-p": Output comma-separated values containing- + # real-time factor (percent), simtime (sec), realtime (sec), paused (T or F) + stats_process = subprocess.Popen(args, stdout=subprocess.PIPE) + # bufsize=1 enables line-bufferred mode (the input buffer is flushed + # automatically on newlines if you would write to process.stdin ) + with stats_process.stdout: + for line in iter(stats_process.stdout.readline, b''): + stats_list = [x.strip() for x in line.split(b',')] + self.real_time_factor = stats_list[0].decode("utf-8") + + # Function to maintain thread execution + def execute_thread(self, source_code): + # Keep checking until the thread is alive + # The thread will die when the coming iteration reads the flag + if (self.thread != None): + while self.thread.is_alive(): + time.sleep(0.2) + + # Turn the flag down, the iteration has successfully stopped! + self.reload = False + # New thread execution + self.thread = threading.Thread(target=self.process_code, args=[source_code]) + self.thread.start() + self.send_code_message() + print("New Thread Started!") + + # Function to read and set frequency from incoming message + def read_frequency_message(self, message): + frequency_message = json.loads(message) + + # Set brain frequency + frequency = float(frequency_message["brain"]) + self.ideal_cycle = 1000.0 / frequency + + # Set gui frequency + frequency = float(frequency_message["gui"]) + self.thread_gui.ideal_cycle = 1000.0 / frequency + + return + + # The websocket function + # Gets called when there is an incoming message from the client + def handle(self, client, server, message): + if (message[:5] == "#freq"): + frequency_message = message[5:] + self.read_frequency_message(frequency_message) + time.sleep(1) + return + + elif(message[:5] == "#ping"): + time.sleep(1) + self.send_ping_message() + return + + elif (message[:5] == "#code"): + try: + # Once received turn the reload flag up and send it to execute_thread function + self.user_code = message[6:] + # print(repr(code)) + self.reload = True + self.execute_thread(self.user_code) + except: + pass + + + elif (message[:5] == "#rest"): + try: + self.reload = True + self.stop_brain = True + self.execute_thread(self.user_code) + except: + pass + + elif (message[:5] == "#stop"): + self.stop_brain = True + + elif (message[:5] == "#play"): + self.stop_brain = False + + # Function that gets called when the server is connected + def connected(self, client, server): + self.client = client + # Start the GUI update thread + self.thread_gui = ThreadGUI(self.gui) + self.thread_gui.start() + + # Start the real time factor tracker thread + self.stats_thread = threading.Thread(target=self.track_stats) + self.stats_thread.start() + + # Start measure frequency + self.measure_thread = threading.Thread(target=self.measure_frequency) + self.measure_thread.start() + + print(client, 'connected') + + # Function that gets called when the connected closes + def handle_close(self, client, server): + print(client, 'closed') + + def run_server(self): + self.server = WebsocketServer(port=1905, host=self.host) + self.server.set_fn_new_client(self.connected) + self.server.set_fn_client_left(self.handle_close) + self.server.set_fn_message_received(self.handle) + + logged = False + while not logged: + try: + f = open("/ws_code.log", "w") + f.write("websocket_code=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + + +# Execute! +if __name__ == "__main__": + server = Template() + server.run_server() diff --git a/exercises/static/exercises/visual_odometry_3D/gui.py b/exercises/static/exercises/visual_odometry_3D/gui.py new file mode 100644 index 000000000..552ba3af4 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/gui.py @@ -0,0 +1,197 @@ +import base64 +import json +import logging +import threading +import time +from datetime import datetime + +import cv2 +import numpy as np +from websocket_server import WebsocketServer + + +# Graphical User Interface Class +class GUI: + # Initialization function + # The actual initialization + def __init__(self, host, hal): + t = threading.Thread(target=self.run_server) + + self.payload = {'image': ''} + self.server = None + self.client = None + + self.host = host + + # Image variables + self.image_to_be_shown = None + self.image_to_be_shown_updated = False + self.image_show_lock = threading.Lock() + + self.acknowledge = False + self.acknowledge_lock = threading.Lock() + + # Take the console object to set the same websocket and client + self.hal = hal + t.start() + + # Function to prepare image payload + # Encodes the image as a JSON string and sends through the WS + def payload_image(self): + self.image_show_lock.acquire() + image_to_be_shown_updated = self.image_to_be_shown_updated + image_to_be_shown = self.image_to_be_shown + self.image_show_lock.release() + + image = image_to_be_shown + payload = {'image': '', 'shape': '', 'counter': str(self.hal.image_counter)} + + if(image_to_be_shown_updated == False): + return payload + + shape = image.shape + frame = cv2.imencode('.JPEG', image)[1] + encoded_image = base64.b64encode(frame) + + payload['image'] = encoded_image.decode('utf-8') + payload['shape'] = shape + payload['counter'] = str(self.hal.image_counter) + payload['true_euler_angles'] = self.hal.get_true_euler_angles_corrected() + payload['true_position'] = self.hal.get_true_position_corrected() + payload['estimated_euler_angles'] = self.hal.get_estimated_euler_angles() + payload['estimated_position'] = self.hal.get_estimated_position() + + self.image_show_lock.acquire() + self.image_to_be_shown_updated = False + self.image_show_lock.release() + + return payload + + # User method + # Function for student to call + def show_image(self, image): + self.image_show_lock.acquire() + self.image_to_be_shown = image + self.image_to_be_shown_updated = True + self.image_show_lock.release() + + # Function to get the client + # Called when a new client is received + def get_client(self, client, server): + self.client = client + + # Function to get value of Acknowledge + def get_acknowledge(self): + self.acknowledge_lock.acquire() + acknowledge = self.acknowledge + self.acknowledge_lock.release() + + return acknowledge + + # Function to set value of Acknowledge + def set_acknowledge(self, value): + self.acknowledge_lock.acquire() + self.acknowledge = value + self.acknowledge_lock.release() + + # Update the gui + def update_gui(self): + # Payload Image Message + payload = self.payload_image() + self.payload["image"] = json.dumps(payload) + message = "#gui" + json.dumps(self.payload) + self.server.send_message(self.client, message) + + # Function to read the message from websocket + # Gets called when there is an incoming message from the client + def get_message(self, client, server, message): + # Acknowledge Message for GUI Thread + if(message[:4] == "#ack"): + self.set_acknowledge(True) + + # Activate the server + def run_server(self): + self.server = WebsocketServer(port=2303, host=self.host) + self.server.set_fn_new_client(self.get_client) + self.server.set_fn_message_received(self.get_message) + + logged = False + while not logged: + try: + f = open("/ws_gui.log", "w") + f.write("websocket_gui=ready") + f.close() + logged = True + except: + time.sleep(0.1) + + self.server.run_forever() + +# This class decouples the user thread +# and the GUI update thread +class ThreadGUI(threading.Thread): + def __init__(self, gui): + self.gui = gui + # Time variables + self.ideal_cycle = 80 + self.measured_cycle = 90 + self.iteration_counter = 0 + + # Function to start the execution of threads + def start(self): + self.measure_thread = threading.Thread(target=self.measure_thread) + self.thread = threading.Thread(target=self.run) + + self.measure_thread.start() + self.thread.start() + + print("GUI Thread Started!") + + # The measuring thread to measure frequency + def measure_thread(self): + while(self.gui.client == None): + pass + + previous_time = datetime.now() + while(True): + # Sleep for 2 seconds + time.sleep(2) + + # Measure the current time and subtract from previous time to get real time interval + current_time = datetime.now() + dt = current_time - previous_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + previous_time = current_time + + # Get the time period + try: + # Division by zero + self.measured_cycle = ms / self.iteration_counter + except: + self.measured_cycle = 0 + + # Reset the counter + self.iteration_counter = 0 + + def run(self): + while (self.gui.client == None): + pass + + last_image = 0 + while (True): + start_time = datetime.now() + self.gui.update_gui() + acknowledge_message = self.gui.get_acknowledge() + + while (acknowledge_message == False): + acknowledge_message = self.gui.get_acknowledge() + + self.gui.set_acknowledge(False) + + finish_time = datetime.now() + self.iteration_counter = self.iteration_counter + 1 + + dt = finish_time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + if (ms < self.ideal_cycle): + time.sleep((self.ideal_cycle - ms) / 1000.0) diff --git a/exercises/static/exercises/visual_odometry_3D/hal.py b/exercises/static/exercises/visual_odometry_3D/hal.py new file mode 100644 index 000000000..976946222 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/hal.py @@ -0,0 +1,209 @@ +import json +import math +import threading +import time +from datetime import datetime +from glob import glob + +import cv2 +import numpy as np +from interfaces.camera import ListenerCamera, ListenerParameters +from interfaces.pinhole_camera import PinholeCamera + +ROTATION_CAMERA_TO_WORLD = np.array([ + [0, 0, 1], + [0, 1, 0], + [1, 0, 0] +]) + +# Checks if a matrix is a valid rotation matrix. +def isRotationMatrix(R): + Rt = np.transpose(R) + shouldBeIdentity = np.dot(Rt, R) + I = np.identity(3, dtype=R.dtype) + n = np.linalg.norm(I - shouldBeIdentity) + return n < 1e-6 + + +def rotation2Euler(R): + """ + Calculates euler angles from rotation matrix. + From horizon to body axes using Tait-Bryan angles. + """ + + assert (isRotationMatrix(R), "Range of matrix is not 3.") + + sy = math.sqrt(R[0, 0] * R[0, 0] + R[1, 0] * R[1, 0]) + + singular = sy < 1e-6 + + if not singular: + x = math.atan2(R[2, 1], R[2, 2]) + y = math.atan2(-R[2, 0], sy) + z = math.atan2(R[1, 0], R[0, 0]) + else: + x = math.atan2(-R[1, 2], R[1, 1]) + y = math.atan2(-R[2, 0], sy) + z = 0 + + return -np.array([x, y, z]) # minus because of the rotation matrix convention + +def read_kitti_groundtruth(groundtruth_file: str) -> list: + with open(groundtruth_file, "r") as f: + return f.readlines() + + +# Hardware Abstraction Layer +class HAL: + def __init__(self): + + # Dataset data + SEQUENCE_NUMBER = "00" + DATASET = 'kitti' + + # Paths + DATASET_PATH = "/datasets/" + DATASET + "/dataset/" + SEQUENCE_PATH = DATASET_PATH + "sequences/" + SEQUENCE_NUMBER + LEFT_CAMERA_IMAGES_PATH = SEQUENCE_PATH + "/image_0/*.png" + RIGHT_CAMERA_IMAGES_PATH = SEQUENCE_PATH + "/image_1/*.png" + + # Files + GROUNDTRUTH_FILE = DATASET_PATH + "poses/" + SEQUENCE_NUMBER + ".txt" + CALIBRATION_FILE = SEQUENCE_PATH + "/calib.txt" + + # Images paths list + self.image_counter = 0 + self.left_image_files_array = sorted(glob(LEFT_CAMERA_IMAGES_PATH)) + self.right_image_files_array = sorted(glob(RIGHT_CAMERA_IMAGES_PATH)) + + # Grounth-truth variables + self.groundtruth = read_kitti_groundtruth(GROUNDTRUTH_FILE) + + self.init_true_position = self.get_groundtruth(0) + self.init_true_rotation_matrix = self.get_true_rotation_matrix(0) + + self.true_position_corrected = np.zeros((1,3)) + self.true_rotation_matrix_corrected = np.zeros( shape=(3,3) ) + + # Odometry variables + self.image = None + self.estimated_position = np.array([0, 0, 0], dtype=np.float32).flatten().round(decimals=5) + self.estimated_euler_angles = np.array([0, 0, 0], dtype=np.float32).flatten().round(decimals=5) + + # User method + # Advance current frame + def advance(self): + self.image_counter += 1 + + # User method + # Get Image from ROS Driver Camera + def get_image(self, lr): + image = None + if (lr == 'left'): + image = cv2.imread(self.left_image_files_array[self.image_counter], 0) + elif (lr == 'right'): + image = cv2.imread(self.right_image_files_array[self.image_counter], 0) + else: + print("Invalid camera") + return image + + # Get grounth-truth position in frame_id time + def get_groundtruth(self, frame_id: int): + + ss = self.groundtruth[frame_id].strip().split() + x = float(ss[3]) + y = float(ss[7]) + z = float(ss[11]) + + return np.array([x, y, z], dtype=np.float32).flatten().round(decimals=5) + + # User method + # Get corrected grounth-truth position in self.image_counter time + def get_current_groundtruth_position(self): + self.true_position_corrected = self.get_groundtruth(self.image_counter) - self.init_true_position + return self.true_position_corrected + + # Get true rotation matrix in frame_id time + def get_true_rotation_matrix(self, frame_id: int) -> np.ndarray: + + ss = self.groundtruth[frame_id].strip().split() + + r11 = float(ss[0]) + r12 = float(ss[1]) + r13 = float(ss[2]) + + r21 = float(ss[4]) + r22 = float(ss[5]) + r23 = float(ss[6]) + + r31 = float(ss[8]) + r32 = float(ss[9]) + r33 = float(ss[10]) + + R = np.array( + [ + [r11, r12, r13], + [r21, r22, r23], + [r31, r32, r33], + ] + , dtype=np.float32) + + R = np.round(R, decimals=7) + + return R + + def get_true_rotation_matrix_corrected(self): + self.true_rotation_matrix_corrected = np.linalg.inv(self.init_true_rotation_matrix) @ self.get_true_rotation_matrix(self.image_counter) + return self.true_rotation_matrix_corrected + + def get_true_euler_angles_corrected_array(self): + roll, pitch, yaw = rotation2Euler( self.get_true_rotation_matrix_corrected() ) + return np.array([roll, pitch, yaw], dtype=np.float32).flatten().round(decimals=5) + + def get_true_euler_angles_corrected(self): + true_euler_angles_corrected = rotation2Euler( self.get_true_rotation_matrix_corrected() ) + trueRoll, truePitch, trueYaw = true_euler_angles_corrected + message = { + "yaw": str(trueYaw), + "pitch": str(truePitch), + "roll": str(trueRoll) + } + return json.dumps(message) + + def get_estimated_euler_angles(self): + estimatedRoll, estimatedPitch, estimatedYaw = self.estimated_euler_angles + message = { + "yaw": str(estimatedYaw), + "pitch": str(estimatedPitch), + "roll": str(estimatedRoll) + } + return json.dumps(message) + + def get_estimated_position(self): + x, y, z = ROTATION_CAMERA_TO_WORLD @ self.estimated_position + message = { + "x": str(x), + "y": str(y), + "z": str(z) + } + return json.dumps(message) + + def get_true_position_corrected(self): + x, y, z = ROTATION_CAMERA_TO_WORLD @ self.get_current_groundtruth_position() + message = { + "x": str(x), + "y": str(y), + "z": str(z) + } + return json.dumps(message) + + # User method + # Set estimated position calculated by user + def set_estimated_position(self, x: float, y: float, z: float): + self.estimated_position = np.array([x, y, z], dtype=np.float32).flatten().round(decimals=5) + + # User method + # Set estimated orientation in euler angles calculated by user + def set_estimated_euler_angles(self, roll: float, pitch: float, yaw: float): + self.estimated_euler_angles = np.array([roll, pitch, yaw], dtype=np.float32).flatten().round(decimals=5) + \ No newline at end of file diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/__init__.py b/exercises/static/exercises/visual_odometry_3D/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/camera.py b/exercises/static/exercises/visual_odometry_3D/interfaces/camera.py new file mode 100644 index 000000000..91b3bed40 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/camera.py @@ -0,0 +1,203 @@ +import rospy +from sensor_msgs.msg import Image as ImageROS +import yaml +import threading +from math import pi as PI +import cv2 +from cv_bridge import CvBridge, CvBridgeError +import os + +MAXRANGE = 8 #max length received from imageD +MINRANGE = 0 + +def imageMsg2Image(img, bridge): + + image = Image() + + image.width = img.width + image.height = img.height + image.format = "BGR8" + image.timeStamp = img.header.stamp.secs + (img.header.stamp.nsecs *1e-9) + cv_image=0 + if (img.encoding[-2:] == "C1"): + gray_img_buff = bridge.imgmsg_to_cv2(img, img.encoding) + cv_image = depthToRGB8(gray_img_buff, img.encoding) + else: + cv_image = bridge.imgmsg_to_cv2(img, "bgr8") + image.data = cv_image + return image + +import numpy as np + +class Image: + + def __init__(self): + + self.height = 3 # Image height [pixels] + self.width = 3 # Image width [pixels] + self.timeStamp = 0 # Time stamp [s] */ + self.format = "" # Image format string (RGB8, BGR,...) + self.data = np.zeros((self.height, self.width, 3), np.uint8) # The image data itself + self.data.shape = self.height, self.width, 3 + + + def __str__(self): + s = "Image: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n format: " + self.format + "\n timeStamp: " + str(self.timeStamp) + s = s + "\n data: " + str(self.data) + "\n}" + return s + + +class ListenerParameters: + def __init__(self,configFile,cam): + + if os.getcwd() == "/": + f = open("/RoboticsAcademy/exercises/visual_odometry_3D/web-template/" + configFile, "r") + else: + f = open(configFile, "r") + + cfg = yaml.safe_load(f) + #starting comm + #jdrc= comm.init(cfg, 'VisualOdometry3D') + #ic = jdrc.getIc() + #properties = ic.getProperties() + + data = cfg["VisualOdometry3D"][cam]["data"] + print(data) + + self.K = np.array([data["K"][0],data["K"][1],data["K"][2],data["K"][4], data["K"][5],data["K"][6],data["K"][8],data["K"][9],data["K"][10]],dtype=np.double).reshape(3,3) + self.RT = np.array([data["RT"][0],data["RT"][1],data["RT"][2],data["RT"][3], data["RT"][4],data["RT"][5],data["RT"][6],data["RT"][7],data["RT"][8],data["RT"][9],data["RT"][10],data["RT"][11],0,0,0,1],dtype=np.double).reshape(4,4) + self.width = data["Size"][0] + self.height = data["Size"][1] + + + def backproject(self, point2d): + myin_h = self.K[0,0] + myin_x = point2d[0] * self.K[0,0] / point2d[2] + myin_y = point2d[1] * self.K[0,0] / point2d[2] + + ik11 = (1. / self.K[0,0]) + ik12 = 0. + ik13 = (-self.K[0,2]) / (self.K[0,0]) + ik21 = 0. + ik22 = (1. / self.K[1,1]) + ik23 = -(self.K[1,2] / (self.K[1,1])) + ik31 = 0. + ik32 = 0. + ik33 = 1. / self.K[2,2] + + a1 = ik11 * myin_x + ik12 * myin_y + ik13 * myin_h + a2 = ik21 * myin_x + ik22 * myin_y + ik23 * myin_h + a3 = ik31 * myin_x + ik32 * myin_y + ik33 * myin_h + a4 = 1. + + ir11 = self.RT[0,0] + ir12 = self.RT[1,0] + ir13 = self.RT[2,0] + ir14 = 0. + ir21 = self.RT[0,1] + ir22 = self.RT[1,1] + ir23 = self.RT[2,1] + ir24 = 0. + ir31 = self.RT[0,2] + ir32 = self.RT[1,2] + ir33 = self.RT[2,2] + ir34 = 0. + ir41 = 0. + ir42 = 0. + ir43 = 0. + ir44 = 1. + + b1 = ir11 * a1 + ir12 * a2 + ir13 * a3 + ir14 * a4 + b2 = ir21 * a1 + ir22 * a2 + ir23 * a3 + ir24 * a4 + b3 = ir31 * a1 + ir32 * a2 + ir33 * a3 + ir34 * a4 + b4 = ir41 * a1 + ir42 * a2 + ir43 * a3 + ir44 * a4 + + it11 = 1. + it12 = 0. + it13 = 0. + it14 = self.RT[0,3] + it21 = 0.; it22 = 1.; it23 = 0.; it24 = 0.; + it31 = 0.; it32 = 0.; it33 = 1.; it34 = 0.; + it41 = 0.; it42 = 0.; it43 = 0.; it44 = 1.; + + outPoint = np.array([0,0,0,1]) + outPoint[0] = it11 * b1 + it12 * b2 + it13 * b3 + it14 * b4; + outPoint[1] = it21 * b1 + it22 * b2 + it23 * b3 + it24 * b4; + outPoint[2] = it31 * b1 + it32 * b2 + it33 * b3 + it34 * b4; + outPoint[3] = it41 * b1 + it42 * b2 + it43 * b3 + it44 * b4; + return outPoint + + def project(self,point3d): + point3d[3] = -1.0 + a1 = self.RT[0,0] * point3d[0] + self.RT[0,1] * point3d[1] + self.RT[0,2] * point3d[2] + self.RT[0,3] * point3d[3] + a2 = self.RT[1,0] * point3d[0] + self.RT[1,1] * point3d[1] + self.RT[1,2] * point3d[2] + self.RT[1,3] * point3d[3] + a3 = self.RT[2,0] * point3d[0] + self.RT[2,1] * point3d[1] + self.RT[2,2] * point3d[2] + self.RT[2,3] * point3d[3] + a4 = self.RT[3,0] * point3d[0] + self.RT[3,1] * point3d[1] + self.RT[3,2] * point3d[2] + self.RT[3,3] * point3d[3] + + out_x = self.K[0,0] * a1 + self.K[0,1] * a2 + self.K[0,2] * a3; + out_y = self.K[1,0] * a1 + self.K[1,1] * a2 + self.K[1,2] * a3; + out_h = self.K[2,0] * a1 + self.K[2,1] * a2 + self.K[2,2] * a3; + + if out_h != 0.: + out_x = out_x / out_h; + out_y = out_y/ out_h; + out_h = 1.; + return np.array([out_x,out_y,out_h]) + + def graficToOptical(self,point2d): + x = point2d[0] + y = point2d[1] + point = np.array([x, self.height - 1 - y, point2d[2]]) + return point + + def opticalToGrafic(self,point2d): + x = point2d[0] + y = point2d[1] + point = np.array([x, self.height - 1 - y, point2d[2]]) + return point + + def getCameraPosition(self): + return np.array([self.RT[0,3],self.RT[1,3], self.RT[2,3]]) + +class ListenerCamera: + + def __init__(self, topic): + + self.topic = topic + self.data = Image() + self.sub = None + self.lock = threading.Lock() + + self.bridge = CvBridge() + self.start() + + def __callback (self, img): + + image = imageMsg2Image(img, self.bridge) + + self.lock.acquire() + self.data = image + self.lock.release() + + def stop(self): + + self.sub.unregister() + + def start (self): + + self.sub = rospy.Subscriber(self.topic, ImageROS, self.__callback) + + def getImage(self): + + self.lock.acquire() + image = self.data + self.lock.release() + + return image + + def hasproxy (self): + + return hasattr(self,"sub") and self.sub + + diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/camera_parameters.py b/exercises/static/exercises/visual_odometry_3D/interfaces/camera_parameters.py new file mode 100644 index 000000000..a186e79fc --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/camera_parameters.py @@ -0,0 +1,164 @@ +import rospy +from sensor_msgs.msg import CameraInfo +import numpy as np +import config +from numpy.linalg import inv + +def param2Msg(K, RT, width, height): + param = CameraInfo() + param.height = height + param.width = width + param.K = K + param.R = RT + + return param + +import numpy as np + +class CameraParameters: + + def __init__(self): + self.height = 3 + self.width = 3 + self.timeStamp = 0 + self.K = np.zeros(9) + self.RT = np.zeros(9) + + def __str__(self): + s = "Camera: {\n height: " + str(self.height) + "\n width: " + str(self.width) + s = s + "\n K: " + str(self.K) + "\n RT: " + str(self.RT) + s = s + "\n timeStamp: " + str(self.timeStamp) + + return s + +class PublisherCamera: + + def __init__(self, configFile, cam): + cfg = config.load(configFile) + data = cfg.getProperty("VisualOdometry3D."+cam+".data") + + self.K=np.array([data["K"][0],data["K"][1],data["K"][2],data["K"][4], data["K"][5],data["K"][6],data["K"][8],data["K"][9],data["K"][10]],dtype=np.double).reshape(3,3) + self.RT=np.array([data["RT"][0],data["RT"][1],data["RT"][2],data["RT"][3], data["RT"][4],data["RT"][5],data["RT"][6],data["RT"][7],data["RT"][8],data["RT"][9],data["RT"][10],data["RT"][11],0,0,0,1],dtype=np.double).reshape(4,4) + self.width=data["Size"][0] + self.height=data["Size"][1] + + def backproject(self, point2d): + myin_h=self.K[0,0] + myin_x=point2d[0]*self.K[0,0]/point2d[2] + myin_y=point2d[1]*self.K[0,0]/point2d[2] + + ik11=(1./self.K[0,0]) + ik12=-self.K[0,1]/(self.K[0,0]*self.K[1,1]) + ik13=(self.K[0,1]*self.K[1,2]-self.K[0,2]*self.K[1,1])/(self.K[1,1]*self.K[0,0]) + ik21=0. + ik22=(1./self.K[1,1]) + ik23=-(self.K[1,2]/self.K[1,1]) + ik31=0. + ik32=0. + ik33=1. + + a1=ik11*myin_x+ik12*myin_y+ik13*myin_h + a2=ik21*myin_x+ik22*myin_y+ik23*myin_h + a3=ik31*myin_x+ik32*myin_y+ik33*myin_h + a4=1. + + ir11=self.RT[0,0] + ir12=self.RT[1,0] + ir13=self.RT[2,0] + ir14=0. + ir21=self.RT[0,1] + ir22=self.RT[1,1] + ir23=self.RT[2,1] + ir24=0. + ir31=self.RT[0,2] + ir32=self.RT[1,2] + ir33=self.RT[2,2] + ir34=0. + ir41=0. + ir42=0. + ir43=0. + ir44=1. + + b1=ir11*a1+ir12*a2+ir13*a3+ir14*a4 + b2=ir21*a1+ir22*a2+ir23*a3+ir24*a4 + b3=ir31*a1+ir32*a2+ir33*a3+ir34*a4 + b4=ir41*a1+ir42*a2+ir43*a3+ir44*a4 + + it11=1. + it12=0. + it13=0. + it14=-self.RT[2,3] + it21=0.; it22=1.; it23=0.; it24=self.RT[1,3]; + it31=0.; it32=0.; it33=1.; it34=-self.RT[0,3]; + it41=0.; it42=0.; it43=0.; it44=1.; + + outPoint = np.array([0,0,0,1]) + outPoint[0]=it11*b1+it12*b2+it13*b3+it14*b4; + outPoint[1]=it21*b1+it22*b2+it23*b3+it24*b4; + outPoint[2]=it31*b1+it32*b2+it33*b3+it34*b4; + outPoint[3]=it41*b1+it42*b2+it43*b3+it44*b4; + return outPoint + + def backproject2(self, point2d): + iK = inv(self.K) + Pi=np.array([point2d[0]/point2d[2],point2d[1]/point2d[2],1.0],dtype=np.double).reshape(3,1) + a=np.dot(iK,Pi) + aH=np.array([a[0],a[1],a[2],1],dtype=np.double) + RT2=self.RT.copy() + RT2[0,3] = 0 + RT2[1,3] = 0 + RT2[2,3] = 0 + RT2[3,3] = 1 + b = np.dot(np.transpose(RT2),aH) + translate = np.identity(4,dtype=np.double) + translate[0,3] = self.RT[0,3]/self.RT[3,3]; + translate[1,3] = self.RT[1,3]/self.RT[3,3]; + translate[2,3] = self.RT[2,3]/self.RT[3,3]; + b=np.dot(translate,b) + outPoint = np.array([b[0]/b[3],b[1]/b[3],b[2]/b[3],1]) + return outPoint + + def project(self,point3d): + + a1=self.RT[0,0]*point3d[0]+self.RT[0,1]*point3d[1]+self.RT[0,2]*point3d[2]+self.RT[0,3]*point3d[3] + a2=self.RT[1,0]*point3d[0]+self.RT[1,1]*point3d[1]+self.RT[1,2]*point3d[2]+self.RT[1,3]*point3d[3] + a3=self.RT[2,0]*point3d[0]+self.RT[2,1]*point3d[1]+self.RT[2,2]*point3d[2]+self.RT[2,3]*point3d[3] + a4=self.RT[3,0]*point3d[0]+self.RT[3,1]*point3d[1]+self.RT[3,2]*point3d[2]+self.RT[3,3]*point3d[3] + out_x=self.K[0,0]*a1+self.K[0,1]*a2+self.K[0,2]*a3; + out_y=self.K[1,0]*a1+self.K[1,1]*a2+self.K[1,2]*a3; + out_h=self.K[2,0]*a1+self.K[2,1]*a2+self.K[2,2]*a3; + + if out_h!=0.: + out_x=out_x/out_h; + out_y=out_y/out_h; + out_h=1.; + return np.array([out_x,out_y,out_h]) + + + def project2(self,point3d): + a1=self.RT[0,0]*point3d[0] + self.RT[0,1]*point3d[1] + self.RT[0,2]*point3d[2] - self.RT[2,3] + a2=self.RT[1,0]*point3d[0] + self.RT[1,1]*point3d[1] + self.RT[1,2]*point3d[2] + self.RT[1,3] + a3=self.RT[2,0]*point3d[0] + self.RT[2,1]*point3d[1] + self.RT[2,2]*point3d[2] - self.RT[0,3] + aP= np.array([a1,a2,a3],dtype=np.double) + # a = self.RT.dot(point3d) + # print ("aproject") + # print (a) + # aP = np.array([a[0]/a[3],a[1]/a[3],a[2]/a[3]],dtype=np.double) + p = self.K.dot(aP) + outPoint = np.array([p[0]/p[2],p[1]/p[2],1.0]); + return outPoint + + def graficToOptical(self,point2d): + x = point2d[0] + y = point2d[1] + point = np.array([self.height-1-y,x, point2d[2]]) + return point + + def opticalToGrafic(self,point2d): + x = point2d[0] + y = point2d[1] + point = np.array([y,self.height - 1 - x, point2d[2]]) + return point + + def getCameraPosition(self): + return np.array([-self.RT[2,3],self.RT[1,3],-self.RT[0,3]]) diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py b/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py new file mode 100644 index 000000000..c2fd5a835 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py @@ -0,0 +1,93 @@ +import numpy as np +import yaml + + +class PinholeCamera: + + def __init__(self, width=None, height=None, fu=None, fv=None, cu=None, cv=None, + distortion_model=None, distortion_coefficients=None, extrinsics=None, intrinsics=None): + + self.width = width + self.height = height + self.fu = fu + self.fv = fv + self.cu = cu + self.cv = cv + self.distortion_model = distortion_model + self.distortion_coefficients = distortion_coefficients + self.extrinsics = extrinsics + self.intrinsics = intrinsics + self.camera_matrix = self.cameraMatrix() + + + def get_camera_matrix(self): + return np.array([[self.fu, 0, self.cu], [0, self.fv, self.cv], [0, 0, 1]]) + + + def print(self): + + print("PinholeCamera:") + print(" width:", self.width) + print(" height:", self.height) + print(" fu:", self.fu) + print(" fv:", self.fv) + print(" cu:", self.cu) + print(" cv:", self.cv) + print(" distortion_model:", self.distortion_model) + print(" distortion_coefficients:", self.distortion_coefficients) + print(" extrinsics:", self.extrinsics) + print(" intrinsics:", self.intrinsics) + + + @classmethod + def from_euromav(cls, file_path: str): + + with open(file_path, "r") as f: + try: + data = yaml.load(f, Loader=yaml.FullLoader) + except yaml.YAMLError as exc: + print(exc) + + width, height = data["resolution"] + fu, fv, cu, cv = data["intrinsics"] + distortion_model = data["distortion_model"] + distortion_coefficients = np.array(data["distortion_coefficients"]) + extrinsics = np.array(data["T_BS"]["data"]).reshape(4, 4) + intrinsics = np.array(data["intrinsics"]) + + return cls(width, height, fu, fv, cu, cv, distortion_model, distortion_coefficients, extrinsics, intrinsics) + + + @classmethod + def from_kitti(cls, file_path: str, width: int, height: int): + + with open(file_path, "r") as f: + data = f.readlines() + for i in range(len(data)): + data[i] = data[i].replace("\n", "").split(" ") + + fu = float(data[0][1]) + fv = float(data[0][1]) + cu = float(data[0][3]) + cv = float(data[0][7]) + + return cls(width, height, fu, fv, cu, cv) + + + @classmethod + def from_vkitti2(cls, file_path: str, width: int, height: int, camera: int): + + with open(file_path, "r") as f: + lines = f.readlines() + + if camera == 0: + data = lines[1].replace("\n", "").split(" ") + else: + data = lines[2].replace("\n", "").split(" ") + + fu = float(data[2]) + fv = float(data[3]) + cu = float(data[4]) + cv = float(data[5]) + + return cls(width, height, fu, fv, cu, cv) diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/threadPublisher.py b/exercises/static/exercises/visual_odometry_3D/interfaces/threadPublisher.py new file mode 100644 index 000000000..69aa0ad48 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/threadPublisher.py @@ -0,0 +1,46 @@ +# +# Copyright (C) 1997-2016 JDE Developers Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/. +# Authors : +# Alberto Martin Florido +# Aitor Martinez Fernandez +# +import threading +import time +from datetime import datetime + +time_cycle = 80 + + +class ThreadPublisher(threading.Thread): + + def __init__(self, pub, kill_event): + self.pub = pub + self.kill_event = kill_event + threading.Thread.__init__(self, args=kill_event) + + def run(self): + while (not self.kill_event.is_set()): + start_time = datetime.now() + + self.pub.publish() + + finish_Time = datetime.now() + + dt = finish_Time - start_time + ms = (dt.days * 24 * 60 * 60 + dt.seconds) * 1000 + dt.microseconds / 1000.0 + #print (ms) + if (ms < time_cycle): + time.sleep((time_cycle - ms) / 1000.0) \ No newline at end of file diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/visual_odometry.py b/exercises/static/exercises/visual_odometry_3D/interfaces/visual_odometry.py new file mode 100644 index 000000000..d240b84dc --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/visual_odometry.py @@ -0,0 +1,153 @@ +import cv2 +import pandas as pd +import numpy as np + +from .maths.cv_maths import feature_tracking +from .maths import rotation2Euler +from ...visual_odometry_3D.interfaces.pinhole_camera import PinholeCamera +from .scales import (get_absolute_scale_euromav, get_absolute_scale_kitti, + get_absolute_scale_vkitti2) +from .scales import (get_true_rotation_kitti) + +STAGE_FIRST_FRAME = 0 +STAGE_SECOND_FRAME = 1 +STAGE_DEFAULT_FRAME = 2 +kMinNumFeature = 1500 + +def read_groundtruth(groundtruth_file: str, dataset: str) -> list: + + if dataset == "vkitti2": + df = pd.read_csv(groundtruth_file, sep=" ") + return df[df["cameraID"] == 0].copy() + + with open(groundtruth_file, "r") as f: + return f.readlines() + + +class VisualOdometry: + + def __init__(self, camera: PinholeCamera, groundtruth_file: str, dataset: str): + + self.dataset = dataset + self.camera = camera + + self.frame_stage = 0 + + self.new_frame = None + self.last_frame = None + + self.cur_R = np.eye(3) + self.cur_t = None + self.px_ref = None + self.px_cur = None + self.focal = camera.fu + self.pp = (camera.cu, camera.cv) + + self.trueX, self.trueY, self.trueZ = 0, 0, 0 + self.true_R = np.zeros( shape=(3,3) ) + self.true_t = np.zeros((1,3)) + + self.detector = cv2.FastFeatureDetector_create(threshold=25, nonmaxSuppression=True) + self.groundtruth = read_groundtruth(groundtruth_file, dataset) + + self.timestamp = None + self.frame_timestamps_list = None + self.timestamp_groundtruth_list = None + + + def calculate_true_rotration(self, frame_id:int) -> None: + + if self.dataset == "kitti": + self.true_R = get_true_rotation_kitti(self.groundtruth, frame_id) + + if self.dataset == "vkitti2": + pass + + if self.dataset == "eurocmav": + pass + + def calculate_absolute_scale(self, frame_id: int) -> float: + + if self.dataset == "kitti": + scale, truth = get_absolute_scale_kitti(self.groundtruth, frame_id) + self.true_t = truth + return scale + + if self.dataset == "vkitti2": + scale, truth = get_absolute_scale_vkitti2(self.groundtruth, frame_id) + self.true_t = truth + return scale + + if self.dataset == "eurocmav": + scale, truth = get_absolute_scale_euromav(self.groundtruth, self.timestamp_groundtruth_list , self.frame_timestamps_list, frame_id) + self.true_t = truth + return scale + + print("Dataset not supported.") + exit(1) + + + def process_first_frame(self) -> None: + + self.px_ref = self.detector.detect(self.new_frame) + self.px_ref = np.array([x.pt for x in self.px_ref], dtype=np.float32) + _ = self.calculate_absolute_scale(0) + self.calculate_true_rotration(0) + self.frame_stage = STAGE_SECOND_FRAME + + + def process_second_frame(self) -> None: + + self.px_ref, self.px_cur = feature_tracking(self.last_frame, self.new_frame, self.px_ref) + E, _ = cv2.findEssentialMat(self.px_cur, self.px_ref, focal=self.focal, pp=self.pp, method=cv2.RANSAC, prob=0.999, threshold=1.0) + _, self.cur_R, self.cur_t, _ = cv2.recoverPose(E, self.px_cur, self.px_ref, focal=self.focal, pp = self.pp) + self.px_ref = self.px_cur + _ = self.calculate_absolute_scale(1) + self.calculate_true_rotration(1) + self.frame_stage = STAGE_DEFAULT_FRAME + + + def process_frame(self, frame_id: int) -> None: + + self.px_ref, self.px_cur = feature_tracking(self.last_frame, self.new_frame, self.px_ref) + E, _ = cv2.findEssentialMat(self.px_cur, self.px_ref, focal=self.focal, pp=self.pp, method=cv2.RANSAC, prob=0.999, threshold=1.0) + _, R, t, _ = cv2.recoverPose(E, self.px_cur, self.px_ref, focal=self.focal, pp = self.pp) + absolute_scale = self.calculate_absolute_scale(frame_id) + self.calculate_true_rotration(frame_id) + + if(absolute_scale > 0.1): + self.cur_t = self.cur_t + absolute_scale * self.cur_R @ t + self.cur_R = R @ self.cur_R + + if(self.px_ref.shape[0] < kMinNumFeature): + self.px_cur = self.detector.detect(self.new_frame) + self.px_cur = np.array([x.pt for x in self.px_cur], dtype=np.float32) + + self.px_ref = self.px_cur + + + def update(self, img: np.ndarray, frame_id: int, R=None, t=None) -> None: + assert ( + img.ndim == 2 + and img.shape[0] == self.camera.height + and img.shape[1] == self.camera.width + ), "Frame: provided image has not the same size as the camera model or image is not grayscale" + + self.new_frame = img + + if self.frame_stage == STAGE_DEFAULT_FRAME: + self.process_frame(frame_id) + + elif self.frame_stage == STAGE_SECOND_FRAME: + self.process_second_frame() + + elif self.frame_stage == STAGE_FIRST_FRAME: + self.process_first_frame() + + self.last_frame = self.new_frame + + def get_true_euler_angles(self): + return rotation2Euler(self.true_R) + + def get_estimated_euler_angles(self): + return rotation2Euler(self.cur_R) \ No newline at end of file diff --git a/exercises/static/exercises/visual_odometry_3D/launch/kobuki_1_reconstruccion3d.world b/exercises/static/exercises/visual_odometry_3D/launch/kobuki_1_reconstruccion3d.world new file mode 100644 index 000000000..c69125d88 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/launch/kobuki_1_reconstruccion3d.world @@ -0,0 +1,122 @@ + + + + + + model://ground_plane_sincolor + + + model://sun + + + 2.3 -2 1 0 0 0 + 1 1 1 1 + 0.1 0.1 0.1 1 + 0 0 -1 + + 0.5 + 0.0 + 0.0 + + + + 2.3 2 1 0 0 0 + 1 1 1 1 + 0.1 0.1 0.1 1 + 0 0 -1 + + 0.4 + 0.0 + 0.0 + + + + + model://turtlebotROS + 0 0 0 0 0 0 + + + + + model://duck + 4.7 3 0 1.57 0 1.57 + + + + + model://cereales + 5.3 2.3 0 0 0 0 + + + + + model://blocks + 4.66 -4 0 0 0 1.57 + + + + + model://charactersMario + 4.1 0.5 0 0 0 -1.57 + + + + + + + + + + diff --git a/exercises/static/exercises/visual_odometry_3D/launch/visual_odometry_3D_ros.launch b/exercises/static/exercises/visual_odometry_3D/launch/visual_odometry_3D_ros.launch new file mode 100644 index 000000000..912423dd9 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/launch/visual_odometry_3D_ros.launch @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/static/exercises/visual_odometry_3D/visual_odometry_3D_conf.yml b/exercises/static/exercises/visual_odometry_3D/visual_odometry_3D_conf.yml new file mode 100644 index 000000000..fd1f7dd13 --- /dev/null +++ b/exercises/static/exercises/visual_odometry_3D/visual_odometry_3D_conf.yml @@ -0,0 +1,49 @@ +VisualOdometry3D: + + CameraLeft: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + Proxy: "CameraL:default -h localhost -p 9001" + Format: RGB8 + Topic: "/VisualOdometry3D/image_rawL" + Name: VisualOdometry3DCameraL + + CameraRight: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + Proxy: "CameraR:default -h localhost -p 9001" + Format: RGB8 + Topic: "/VisualOdometry3D/image_rawR" + Name: VisualOdometry3DCameraR + + Motors: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + Proxy: "Motors:default -h localhost -p 9001" + Topic: "/VisualOdometry3D/Motos" + Name: VisualOdometry3DMotors + + Encoders: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + Proxy: "Encoders:default -h localhost -p 9001" + Topic: "/VisualOdometry3D/Encoders" + Name: VisualOdometry3DEncoders + + Viewer: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + Endpoint: "default -h localhost -p 9957:ws -h localhost -p 11000" + Proxy: "3DVizA" + Refresh: True + Topic: "/VisualOdometry3D/Viewer" + Name: VisualOdometry3DViewer + + CamACalibration: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + data: {"K":[240,0,320,0,0,240,240,0,0,0,1,0], + "RT":[1,0,0,-110,0,1,0,0,0,0,-1,0,0,0,0,1], + "Size":[640,480]} + + CamBCalibration: + Server: 2 # 0 -> Deactivate, 1 -> Ice , 2 -> ROS + data: {"K":[240,0,320,0,0,240,240,0,0,0,1,0], + "RT":[1,0,0,110,0,1,0,0,0,0,-1,0,0,0,0,1], + "Size":[640,480]} + + NodeName: "VisualOdometry3D" diff --git a/exercises/templates/exercises/visual_odometry_3D/exercise.html b/exercises/templates/exercises/visual_odometry_3D/exercise.html new file mode 100644 index 000000000..f68958075 --- /dev/null +++ b/exercises/templates/exercises/visual_odometry_3D/exercise.html @@ -0,0 +1,453 @@ +{% extends "exercise_base.html" %} +{% load static %} + +{% block content %} + + +

+ + + + + + + + + +
+
+ +
+
+
+

Score: 0

+
+
+ +
+
+
+ + +
+ + +
from GUI import GUI +from HAL import HAL +# Enter sequential codee! + +while True: +# Enter iterative code! +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+
+
+
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if user_code|length > 0 %} + + + + {% endif %} + +
+ +{% endblock %} diff --git a/instructions.json b/instructions.json new file mode 100644 index 000000000..cdaff162e --- /dev/null +++ b/instructions.json @@ -0,0 +1,165 @@ +{ + "follow_line": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/follow_line/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/follow_line/launch/simple_line_follower_ros_headless_{}.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/gui.py 0.0.0.0 {}" + }, + "obstacle_avoidance": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/obstacle_avoidance/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/obstacle_avoidance/launch/obstacle_avoidance_f1_headless.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/obstacle_avoidance/exercise.py 0.0.0.0" + }, + "vacuum_cleaner": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/vacuum_cleaner/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/vacuum_cleaner/launch/vacuum_cleaner_headless.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/vacuum_cleaner/exercise.py 0.0.0.0" + }, + "vacuum_cleaner_loc": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/vacuum_cleaner_loc/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/vacuum_cleaner_loc/launch/vacuum_cleaner_headless.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/vacuum_cleaner_loc/exercise.py 0.0.0.0" + }, + "color_filter": { + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/color_filter/exercise.py 0.0.0.0" + }, + "drone_cat_mouse": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/launch", + "instructions_ros": ["python3 /RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/exercise.py 0.0.0.0", + "instructions_guest": "python3 /RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/exercise_guest.py 0.0.0.0" + }, + "drone_cat_mouse_rotors": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/web-template/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/web-template/launch/drone_cat_mouse.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/web-template/exercise.py 0.0.0.0", + "instructions_guest": "python3 /RoboticsAcademy/exercises/static/exercises/drone_cat_mouse/web-template/exercise_guest.py 0.0.0.0" + }, + "3d_reconstruction": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/3d_reconstruction/launch", + "instructions_ros": [ + "/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/3d_reconstruction/launch/3d_reconstruction_ros.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/3d_reconstruction/exercise.py 0.0.0.0" + }, + "follow_turtlebot": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/follow_turtlebot/launch", + "instructions_ros": ["python3 /RoboticsAcademy/exercises/static/exercises/follow_turtlebot/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/follow_turtlebot/exercise.py 0.0.0.0" + }, + "follow_turtlebot_rotors": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/follow_turtlebot/web-template/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/follow_turtlebot/web-template/launch/follow_turtlebot.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/follow_turtlebot/web-template/exercise.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/static/exercises/follow_turtlebot/web-template/gui.py 0.0.0.0 {}" + }, + "global_navigation": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/global_navigation/launch", + "instructions_ros": [ + "/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/global_navigation/launch/taxiholo_1_citylarge_headless.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/global_navigation/exercise.py 0.0.0.0" + }, + "follow_road": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/follow_road/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/follow_road/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/follow_road/exercise.py 0.0.0.0" + }, + "dl_digit_classifier": { + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/dl_digit_classifier/exercise.py 0.0.0.0" + }, + "human_detection": { + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/human_detection/exercise.py 0.0.0.0" + }, + "labyrinth_escape": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/labyrinth_escape/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/labyrinth_escape/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/labyrinth_escape/exercise.py 0.0.0.0" + }, + "position_control": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/position_control/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/position_control/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/position_control/exercise.py 0.0.0.0" + }, + "car_junction": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/car_junction/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/car_junction/launch/car_junction.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/car_junction/exercise.py 0.0.0.0" + }, + "road_junction": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/road_junction/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/road_junction/launch/road_junction.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/road_junction/exercise.py 0.0.0.0" + }, + "rescue_people": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/rescue_people/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/rescue_people/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/rescue_people/exercise.py 0.0.0.0" + }, + "drone_hangar": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/drone_hangar/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/drone_hangar/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/drone_hangar/exercise.py 0.0.0.0" + }, + "drone_gymkhana": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/drone_gymkhana/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/drone_gymkhana/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/drone_gymkhana/exercise.py 0.0.0.0" + }, + "visual_lander": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/visual_lander/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/visual_lander/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/visual_lander/exercise.py 0.0.0.0" + }, + "opticalflow_teleop": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/opticalflow_teleop/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/opticalflow_teleop/launch/simple_line_follower_ros_headless_default.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/opticalflow_teleop/exercise.py 0.0.0.0" + }, + "follow_line_game": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/follow_line/web-template/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/follow_line/web-template/launch/simple_line_follower_game_ros_headless_{}.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/web-template/exercise.py 0.0.0.0", + "instructions_guest": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/web-template/exercise_guest.py 0.0.0.0", + "instructions_gui": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/web-template/gui.py 0.0.0.0 {}", + "instructions_gui_guest": "python3 /RoboticsAcademy/exercises/static/exercises/follow_line/web-template/gui_guest.py 0.0.0.0" + }, + "autoparking": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/autoparking/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/autoparking/launch/autoparking.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/autoparking/exercise.py 0.0.0.0" + }, + "autoparking_v2": { + "gazebo_path": "/RoboticsAcademy/exercises/autoparking_v2/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/autoparking_v2/launch/autoparking.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/autoparking_v2/exercise.py 0.0.0.0" + }, + "montecarlo_visual_loc": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/montecarlo_visual_loc/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch ./RoboticsAcademy/exercises/static/exercises/montecarlo_visual_loc/launch/visual_loc_headless.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/montecarlo_visual_loc/exercise.py 0.0.0.0" + }, + "laser_mapping": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/laser_mapping/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch /opt/jderobot/CustomRobots/stdr_simulator/stdr_launchers/launch/amigobot_without_gui.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/laser_mapping/exercise.py 0.0.0.0" + }, + "laser_loc": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/laser_loc/launch", + "instructions_ros": ["/opt/ros/noetic/bin/roslaunch /opt/jderobot/CustomRobots/stdr_simulator/stdr_launchers/launch/amigobot_without_gui.launch"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/laser_loc/exercise.py 0.0.0.0" + }, + "package_delivery": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/package_delivery/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/package_delivery/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/package_delivery/exercise.py 0.0.0.0" + }, + "power_tower_inspection": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/power_tower_inspection/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/power_tower_inspection/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/power_tower_inspection/exercise.py 0.0.0.0" + }, + "visual_odometry_3D": { + "gazebo_path": "/RoboticsAcademy/exercises/static/exercises/visual_odometry_3D/launch", + "instructions_ros": ["python3 ./RoboticsAcademy/exercises/static/exercises/visual_odometry_3D/launch/launch.py"], + "instructions_host": "python3 /RoboticsAcademy/exercises/static/exercises/visual_odometry_3D/exercise.py 0.0.0.0" + } +} diff --git a/manager/manager.py b/manager/manager.py index b4dfa36a3..b4402ea5c 100644 --- a/manager/manager.py +++ b/manager/manager.py @@ -483,7 +483,7 @@ async def handle(self, websocket, path): self.exercise = data["exercise"] if (self.exercise in STDR_EX): self.simulator = "stdr" - elif (self.exercise == "color_filter" or self.exercise == "human detection" or self.exercise == "digit_classifier"): + elif (self.exercise == "color_filter" or self.exercise == "human detection" or self.exercise == "digit_classifier" or self.exercise == "visual_odometry_3D"): self.simulator = "none" else: self.simulator = "gazebo" @@ -541,7 +541,7 @@ def open_simulation(self, exercise, width, height, circuit): print("> XServer started") # Start the exercise - if exercise not in ["color_filter", "dl_digit_classifier", "human_detection"] and exercise not in STDR_EX: + if exercise not in ["color_filter", "dl_digit_classifier", "human_detection", "visual_odometry_3D"] and exercise not in STDR_EX: print("> Starting GZServer") self.commands.start_gzserver(exercise, circuit) print("> GZServer started") @@ -605,7 +605,7 @@ def open_accelerated_simulation(self, exercise, width, height, circuit): print("> VNC started") # Start the exercise - if exercise not in ["color_filter", "dl_digit_classifier", "human_detection"] and exercise not in STDR_EX: + if exercise not in ["color_filter", "dl_digit_classifier", "human_detection", "visual_odometry_3D"] and exercise not in STDR_EX: print("> Starting GZServer") self.commands.start_gzserver(exercise, circuit) print("> GZServer started") diff --git a/static/exercises/visual_odometry_3D/3DScene/3DViz.js b/static/exercises/visual_odometry_3D/3DScene/3DViz.js new file mode 100644 index 000000000..7e90bb962 --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/3DViz.js @@ -0,0 +1,34 @@ +let config = {}; +var lineInterval, pointInterval, posInterval, objInterval; +var cont = 1; +class obj3DPose { + constructor(id, x, y, z, rx, ry, rz) { + this.id = id; + this.x = x; + this.y = y; + this.z = z; + this.rx = rx; + this.ry = ry; + this.rz = rz; + } +} + +try { + const yaml = require('js-yaml'); + const fs = require('fs'); + config = yaml.safeLoad(fs.readFileSync('public/config.yml', 'utf8')) +} catch (e) { + config.Server = "localhost"; + config.Port = "11000"; + config.updatePoints = 10 + config.updateSegments = 10 + config.linewidth = 2 + config.pointsize = 1.5 + config.spheresize = 0.35 + config.camera = {} + config.camera.x = -30 + config.camera.y = 18 + config.camera.z = -10 + +} + diff --git a/static/exercises/visual_odometry_3D/3DScene/3d_scene.js b/static/exercises/visual_odometry_3D/3DScene/3d_scene.js new file mode 100644 index 000000000..1c96fa7fe --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/3d_scene.js @@ -0,0 +1,211 @@ +var camera, scene, renderer, controls; +var axes, grid, particles; +var windowWidth, windowHeight; +let userFrame, trueFrame; +var rotationx = 0.0; +var rotationy = 0.0; +var toDegrees = 180 / Math.PI; + +const TRACKER_LENGHT = 300 +const USER_COLOR = 0xff0000 // Red +const TRUE_COLOR = 0x00ff00 // Green + +let track = [] +let trackGT = [] +let user_tracker = new CBuffer(TRACKER_LENGHT) +let true_tracker = new CBuffer(TRACKER_LENGHT) + +function init() { + windowWidth = document.getElementById("canvas").offsetWidth + windowHeight = document.getElementById("canvas").offsetHeight + camera = new THREE.PerspectiveCamera(75, windowWidth / windowHeight, 0.01, 1000); + camera.position.z = config.camera.z; + camera.position.y = config.camera.y; + camera.position.x = config.camera.x; + scene = new THREE.Scene(); + renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true }); + renderer.setSize(windowWidth, windowHeight); + renderer.setClearColor(0x3498db); + document.getElementById("canvas").appendChild(renderer.domElement); + controls = new THREE.OrbitControls(camera, renderer.domElement); + window.addEventListener('resize', onWindowResize, false); + var ambientLight = new THREE.AmbientLight(0xffffff, 0.4); + scene.add(ambientLight); + var light = new THREE.PointLight(0xffffff, 1, 100); + light.position.set(10, 10, 10); + scene.add(light); + var light = new THREE.PointLight(0xffffff, 1, 100); + light.position.set(20, 20, 20); + scene.add(light); + var light = new THREE.PointLight(0xffffff, 1, 100); + light.position.set(30, 30, 30); + scene.add(light); + var light = new THREE.PointLight(0xffffff, 1, 100); + light.position.set(40, 40, 40); + scene.add(light); +} + +function onWindowResize() { + windowWidth = document.getElementById("canvas").offsetWidth + windowHeight = document.getElementById("canvas").offsetHeight + + camera.aspect = windowWidth / windowHeight; + camera.updateProjectionMatrix(); + renderer.setSize(windowWidth, windowHeight); +} + +function animate() { + requestAnimationFrame(animate); + renderer.render(scene, camera); +} + +function addPoint(point) { + var geometry = new THREE.Geometry(); + geometry.vertices.push(new THREE.Vector3(point.x, point.y, point.z)); + + var material = new THREE.PointsMaterial({ size: config.pointsize, sizeAttenuation: false }); + material.color.setRGB(point.r / 255, point.g / 255, point.b / 255); + var particles = new THREE.Points(geometry, material); + particles.position.set(point.x, point.y, point.z); + particles.name = "points"; + scene.add(particles); +} + +function addLine(segment, name) { + var geometry = new THREE.Geometry(); + geometry.vertices.push( + new THREE.Vector3(segment.seg.fromPoint.x, segment.seg.fromPoint.z, segment.seg.fromPoint.y), + new THREE.Vector3(segment.seg.toPoint.x, segment.seg.toPoint.z, segment.seg.toPoint.y), + new THREE.Vector3(segment.seg.fromPoint.x, segment.seg.fromPoint.z, segment.seg.fromPoint.y)); + var material = new THREE.LineBasicMaterial(); + material.color.setRGB(segment.c.r, segment.c.g, segment.c.b); + if (name != "plane") { + material.linewidth = config.linewidth; + } + line = new THREE.Line(geometry, material); + line.name = name; + scene.add(line); +} + +function addGrid() { + grid = new THREE.GridHelper(1000, 100, 0x888888, 0x888888); + grid.position.set(0, -0.1, 0); + scene.add(grid); +} + +function deleteObj(name) { + var selectedObject = scene.getObjectByName(name); + while (selectedObject != null) { + scene.remove(selectedObject); + selectedObject = scene.getObjectByName(name); + } +} + +function addObj(obj, pos) { + var type = obj.obj.split(":"); + if (type[0] == "https") { + var url = obj.obj + } else { + var file = new Blob([obj.obj], { type: 'text/plain' }); + var url = window.URL.createObjectURL(file); + } + if (obj.format == "obj") { + loadObj(url, obj, pos) + } else if (obj.format == "dae") { + loadDae(url, obj, pos); + } +} + +function loadDae(url, obj, pose3d) { + var loader = new THREE.ColladaLoader(); + var scale = obj.scale; + loader.load(url, function (collada) { + var object = collada.scene; + object.name = obj.id; + object.position.set(pose3d.x, pose3d.y, pose3d.z); + object.rotation.set(pose3d.rx * toDegrees, pose3d.ry * toDegrees, pose3d.rz * toDegrees); + object.scale.set(scale, scale, scale); + scene.add(object); + }); +} + +function loadObj(url, obj, pose3d) { + var loader = new THREE.OBJLoader(); + var scale = obj.scale; + loader.load(url, + function (object) { + object.name = obj.id; + object.position.set(pose3d.x, pose3d.y, pose3d.z); + object.rotation.set(pose3d.rx * toDegrees, pose3d.ry * toDegrees, pose3d.rz * toDegrees); + object.scale.set(scale, scale, scale); + scene.add(object); + }, + function (xhr) { }, + function (error) { + console.log(error); + } + ); +} + +function moveObj(pose3d) { + selectedObject = scene.getObjectByName(pose3d.id); + selectedObject.position.set(pose3d.x, pose3d.y, pose3d.z); + selectedObject.rotation.set(pose3d.rx * toDegrees, pose3d.ry * toDegrees, pose3d.rz * toDegrees); +} + + +function addAxis() { + const axesHelper = new THREE.AxesHelper(10); // The X axis is red. The Y axis is green. The Z axis is blue. + scene.add(axesHelper); +} + + +function addSphere(point) { + + const geometry = new THREE.SphereGeometry(config.spheresize, 32, 32); + const material = new THREE.MeshBasicMaterial(); + material.color.setRGB(point.r / 255, point.g / 255, point.b / 255); + var sphere = new THREE.Mesh(geometry, material); + sphere.position.set(point.x, point.y, point.z); + scene.add(sphere); + +} + +function addFrames() { + userFrame = createFrame(1241, 356, 1, USER_COLOR); + scene.add(userFrame) + trueFrame = createFrame(1241, 356, 1, TRUE_COLOR); + scene.add(trueFrame) +} + +function createTrack(track, color = 0xff0000) { + const material = new THREE.LineBasicMaterial({ color: color, linewidth: 2 }); + const geometry = new THREE.BufferGeometry().setFromPoints(track.getPoints()); + const line = new THREE.Line(geometry, material); + return line +} + +function reset_scene3d() { + for (var i = scene.children.length - 1; i >= 0; i--) { + if (scene.children[i].type == "Mesh") { + scene.remove(scene.children[i]); + } + } +} + + +function plotTrack(track, color = 0xff0000) { + const material = new THREE.LineBasicMaterial({ color: color, linewidth: 2 }); + const geometry = new THREE.BufferGeometry().setFromPoints(track.getPoints()); + const line = new THREE.Line(geometry, material); + return line +} + + +function webGLStart() { + init(); + addGrid(); + animate(); + addAxis(); + addFrames(); +} diff --git a/static/exercises/visual_odometry_3D/3DScene/ColladaLoader.js b/static/exercises/visual_odometry_3D/3DScene/ColladaLoader.js new file mode 100644 index 000000000..46761959e --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/ColladaLoader.js @@ -0,0 +1,3627 @@ +/** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + +THREE.ColladaLoader = function ( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + +}; + +THREE.ColladaLoader.prototype = { + + constructor: THREE.ColladaLoader, + + crossOrigin: 'Anonymous', + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var path = scope.path === undefined ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path; + + var loader = new THREE.FileLoader( scope.manager ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text, path ) ); + + }, onProgress, onError ); + + }, + + setPath: function ( value ) { + + this.path = value; + + }, + + options: { + + set convertUpAxis( value ) { + + console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' ); + + } + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( text, path ) { + + function getElementsByTagName( xml, name ) { + + // Non recursive xml.getElementsByTagName() ... + + var array = []; + var childNodes = xml.childNodes; + + for ( var i = 0, l = childNodes.length; i < l; i ++ ) { + + var child = childNodes[ i ]; + + if ( child.nodeName === name ) { + + array.push( child ); + + } + + } + + return array; + + } + + function parseStrings( text ) { + + if ( text.length === 0 ) return []; + + var parts = text.trim().split( /\s+/ ); + var array = new Array( parts.length ); + + for ( var i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parts[ i ]; + + } + + return array; + + } + + function parseFloats( text ) { + + if ( text.length === 0 ) return []; + + var parts = text.trim().split( /\s+/ ); + var array = new Array( parts.length ); + + for ( var i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseFloat( parts[ i ] ); + + } + + return array; + + } + + function parseInts( text ) { + + if ( text.length === 0 ) return []; + + var parts = text.trim().split( /\s+/ ); + var array = new Array( parts.length ); + + for ( var i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseInt( parts[ i ] ); + + } + + return array; + + } + + function parseId( text ) { + + return text.substring( 1 ); + + } + + function generateId() { + + return 'three_default_' + ( count ++ ); + + } + + function isEmpty( object ) { + + return Object.keys( object ).length === 0; + + } + + // asset + + function parseAsset( xml ) { + + return { + unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ), + upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] ) + }; + + } + + function parseAssetUnit( xml ) { + + if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) { + + return parseFloat( xml.getAttribute( 'meter' ) ); + + } else { + + return 1; // default 1 meter + + } + + } + + function parseAssetUpAxis( xml ) { + + return xml !== undefined ? xml.textContent : 'Y_UP'; + + } + + // library + + function parseLibrary( xml, libraryName, nodeName, parser ) { + + var library = getElementsByTagName( xml, libraryName )[ 0 ]; + + if ( library !== undefined ) { + + var elements = getElementsByTagName( library, nodeName ); + + for ( var i = 0; i < elements.length; i ++ ) { + + parser( elements[ i ] ); + + } + + } + + } + + function buildLibrary( data, builder ) { + + for ( var name in data ) { + + var object = data[ name ]; + object.build = builder( data[ name ] ); + + } + + } + + // get + + function getBuild( data, builder ) { + + if ( data.build !== undefined ) return data.build; + + data.build = builder( data ); + + return data.build; + + } + + // animation + + function parseAnimation( xml ) { + + var data = { + sources: {}, + samplers: {}, + channels: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + var id; + + switch ( child.nodeName ) { + + case 'source': + id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'sampler': + id = child.getAttribute( 'id' ); + data.samplers[ id ] = parseAnimationSampler( child ); + break; + + case 'channel': + id = child.getAttribute( 'target' ); + data.channels[ id ] = parseAnimationChannel( child ); + break; + + default: + console.log( child ); + + } + + } + + library.animations[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseAnimationSampler( xml ) { + + var data = { + inputs: {}, + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + var id = parseId( child.getAttribute( 'source' ) ); + var semantic = child.getAttribute( 'semantic' ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseAnimationChannel( xml ) { + + var data = {}; + + var target = xml.getAttribute( 'target' ); + + // parsing SID Addressing Syntax + + var parts = target.split( '/' ); + + var id = parts.shift(); + var sid = parts.shift(); + + // check selection syntax + + var arraySyntax = ( sid.indexOf( '(' ) !== - 1 ); + var memberSyntax = ( sid.indexOf( '.' ) !== - 1 ); + + if ( memberSyntax ) { + + // member selection access + + parts = sid.split( '.' ); + sid = parts.shift(); + data.member = parts.shift(); + + } else if ( arraySyntax ) { + + // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. + + var indices = sid.split( '(' ); + sid = indices.shift(); + + for ( var i = 0; i < indices.length; i ++ ) { + + indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) ); + + } + + data.indices = indices; + + } + + data.id = id; + data.sid = sid; + + data.arraySyntax = arraySyntax; + data.memberSyntax = memberSyntax; + + data.sampler = parseId( xml.getAttribute( 'source' ) ); + + return data; + + } + + function buildAnimation( data ) { + + var tracks = []; + + var channels = data.channels; + var samplers = data.samplers; + var sources = data.sources; + + for ( var target in channels ) { + + if ( channels.hasOwnProperty( target ) ) { + + var channel = channels[ target ]; + var sampler = samplers[ channel.sampler ]; + + var inputId = sampler.inputs.INPUT; + var outputId = sampler.inputs.OUTPUT; + + var inputSource = sources[ inputId ]; + var outputSource = sources[ outputId ]; + + var animation = buildAnimationChannel( channel, inputSource, outputSource ); + + createKeyframeTracks( animation, tracks ); + + } + + } + + return tracks; + + } + + function getAnimation( id ) { + + return getBuild( library.animations[ id ], buildAnimation ); + + } + + function buildAnimationChannel( channel, inputSource, outputSource ) { + + var node = library.nodes[ channel.id ]; + var object3D = getNode( node.id ); + + var transform = node.transforms[ channel.sid ]; + var defaultMatrix = node.matrix.clone().transpose(); + + var time, stride; + var i, il, j, jl; + + var data = {}; + + // the collada spec allows the animation of data in various ways. + // depending on the transform type (matrix, translate, rotate, scale), we execute different logic + + switch ( transform ) { + + case 'matrix': + + for ( i = 0, il = inputSource.array.length; i < il; i ++ ) { + + time = inputSource.array[ i ]; + stride = i * outputSource.stride; + + if ( data[ time ] === undefined ) data[ time ] = {}; + + if ( channel.arraySyntax === true ) { + + var value = outputSource.array[ stride ]; + var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ]; + + data[ time ][ index ] = value; + + } else { + + for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) { + + data[ time ][ j ] = outputSource.array[ stride + j ]; + + } + + } + + } + + break; + + case 'translate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'rotate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'scale': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + } + + var keyframes = prepareAnimationData( data, defaultMatrix ); + + var animation = { + name: object3D.uuid, + keyframes: keyframes + }; + + return animation; + + } + + function prepareAnimationData( data, defaultMatrix ) { + + var keyframes = []; + + // transfer data into a sortable array + + for ( var time in data ) { + + keyframes.push( { time: parseFloat( time ), value: data[ time ] } ); + + } + + // ensure keyframes are sorted by time + + keyframes.sort( ascending ); + + // now we clean up all animation data, so we can use them for keyframe tracks + + for ( var i = 0; i < 16; i ++ ) { + + transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] ); + + } + + return keyframes; + + // array sort function + + function ascending( a, b ) { + + return a.time - b.time; + + } + + } + + var position = new THREE.Vector3(); + var scale = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); + + function createKeyframeTracks( animation, tracks ) { + + var keyframes = animation.keyframes; + var name = animation.name; + + var times = []; + var positionData = []; + var quaternionData = []; + var scaleData = []; + + for ( var i = 0, l = keyframes.length; i < l; i ++ ) { + + var keyframe = keyframes[ i ]; + + var time = keyframe.time; + var value = keyframe.value; + + matrix.fromArray( value ).transpose(); + matrix.decompose( position, quaternion, scale ); + + times.push( time ); + positionData.push( position.x, position.y, position.z ); + quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w ); + scaleData.push( scale.x, scale.y, scale.z ); + + } + + if ( positionData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.position', times, positionData ) ); + if ( quaternionData.length > 0 ) tracks.push( new THREE.QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) ); + if ( scaleData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.scale', times, scaleData ) ); + + return tracks; + + } + + function transformAnimationData( keyframes, property, defaultValue ) { + + var keyframe; + + var empty = true; + var i, l; + + // check, if values of a property are missing in our keyframes + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === undefined ) { + + keyframe.value[ property ] = null; // mark as missing + + } else { + + empty = false; + + } + + } + + if ( empty === true ) { + + // no values at all, so we set a default value + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + keyframe.value[ property ] = defaultValue; + + } + + } else { + + // filling gaps + + createMissingKeyframes( keyframes, property ); + + } + + } + + function createMissingKeyframes( keyframes, property ) { + + var prev, next; + + for ( var i = 0, l = keyframes.length; i < l; i ++ ) { + + var keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === null ) { + + prev = getPrev( keyframes, i, property ); + next = getNext( keyframes, i, property ); + + if ( prev === null ) { + + keyframe.value[ property ] = next.value[ property ]; + continue; + + } + + if ( next === null ) { + + keyframe.value[ property ] = prev.value[ property ]; + continue; + + } + + interpolate( keyframe, prev, next, property ); + + } + + } + + } + + function getPrev( keyframes, i, property ) { + + while ( i >= 0 ) { + + var keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i --; + + } + + return null; + + } + + function getNext( keyframes, i, property ) { + + while ( i < keyframes.length ) { + + var keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i ++; + + } + + return null; + + } + + function interpolate( key, prev, next, property ) { + + if ( ( next.time - prev.time ) === 0 ) { + + key.value[ property ] = prev.value[ property ]; + return; + + } + + key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ]; + + } + + // animation clips + + function parseAnimationClip( xml ) { + + var data = { + name: xml.getAttribute( 'id' ) || 'default', + start: parseFloat( xml.getAttribute( 'start' ) || 0 ), + end: parseFloat( xml.getAttribute( 'end' ) || 0 ), + animations: [] + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_animation': + data.animations.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + } + + } + + library.clips[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildAnimationClip( data ) { + + var tracks = []; + + var name = data.name; + var duration = ( data.end - data.start ) || - 1; + var animations = data.animations; + + for ( var i = 0, il = animations.length; i < il; i ++ ) { + + var animationTracks = getAnimation( animations[ i ] ); + + for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) { + + tracks.push( animationTracks[ j ] ); + + } + + } + + return new THREE.AnimationClip( name, duration, tracks ); + + } + + function getAnimationClip( id ) { + + return getBuild( library.clips[ id ], buildAnimationClip ); + + } + + // controller + + function parseController( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'skin': + // there is exactly one skin per controller + data.id = parseId( child.getAttribute( 'source' ) ); + data.skin = parseSkin( child ); + break; + + case 'morph': + data.id = parseId( child.getAttribute( 'source' ) ); + console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' ); + break; + + } + + } + + library.controllers[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSkin( xml ) { + + var data = { + sources: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_shape_matrix': + data.bindShapeMatrix = parseFloats( child.textContent ); + break; + + case 'source': + var id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'joints': + data.joints = parseJoints( child ); + break; + + case 'vertex_weights': + data.vertexWeights = parseVertexWeights( child ); + break; + + } + + } + + return data; + + } + + function parseJoints( xml ) { + + var data = { + inputs: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + var semantic = child.getAttribute( 'semantic' ); + var id = parseId( child.getAttribute( 'source' ) ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseVertexWeights( xml ) { + + var data = { + inputs: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + var semantic = child.getAttribute( 'semantic' ); + var id = parseId( child.getAttribute( 'source' ) ); + var offset = parseInt( child.getAttribute( 'offset' ) ); + data.inputs[ semantic ] = { id: id, offset: offset }; + break; + + case 'vcount': + data.vcount = parseInts( child.textContent ); + break; + + case 'v': + data.v = parseInts( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildController( data ) { + + var build = { + id: data.id + }; + + var geometry = library.geometries[ build.id ]; + + if ( data.skin !== undefined ) { + + build.skin = buildSkin( data.skin ); + + // we enhance the 'sources' property of the corresponding geometry with our skin data + + geometry.sources.skinIndices = build.skin.indices; + geometry.sources.skinWeights = build.skin.weights; + + } + + return build; + + } + + function buildSkin( data ) { + + var BONE_LIMIT = 4; + + var build = { + joints: [], // this must be an array to preserve the joint order + indices: { + array: [], + stride: BONE_LIMIT + }, + weights: { + array: [], + stride: BONE_LIMIT + } + }; + + var sources = data.sources; + var vertexWeights = data.vertexWeights; + + var vcount = vertexWeights.vcount; + var v = vertexWeights.v; + var jointOffset = vertexWeights.inputs.JOINT.offset; + var weightOffset = vertexWeights.inputs.WEIGHT.offset; + + var jointSource = data.sources[ data.joints.inputs.JOINT ]; + var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ]; + + var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array; + var stride = 0; + + var i, j, l; + + // procces skin data for each vertex + + for ( i = 0, l = vcount.length; i < l; i ++ ) { + + var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex + var vertexSkinData = []; + + for ( j = 0; j < jointCount; j ++ ) { + + var skinIndex = v[ stride + jointOffset ]; + var weightId = v[ stride + weightOffset ]; + var skinWeight = weights[ weightId ]; + + vertexSkinData.push( { index: skinIndex, weight: skinWeight } ); + + stride += 2; + + } + + // we sort the joints in descending order based on the weights. + // this ensures, we only procced the most important joints of the vertex + + vertexSkinData.sort( descending ); + + // now we provide for each vertex a set of four index and weight values. + // the order of the skin data matches the order of vertices + + for ( j = 0; j < BONE_LIMIT; j ++ ) { + + var d = vertexSkinData[ j ]; + + if ( d !== undefined ) { + + build.indices.array.push( d.index ); + build.weights.array.push( d.weight ); + + } else { + + build.indices.array.push( 0 ); + build.weights.array.push( 0 ); + + } + + } + + } + + // setup bind matrix + + build.bindMatrix = new THREE.Matrix4().fromArray( data.bindShapeMatrix ).transpose(); + + // process bones and inverse bind matrix data + + for ( i = 0, l = jointSource.array.length; i < l; i ++ ) { + + var name = jointSource.array[ i ]; + var boneInverse = new THREE.Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose(); + + build.joints.push( { name: name, boneInverse: boneInverse } ); + + } + + return build; + + // array sort function + + function descending( a, b ) { + + return b.weight - a.weight; + + } + + } + + function getController( id ) { + + return getBuild( library.controllers[ id ], buildController ); + + } + + // image + + function parseImage( xml ) { + + var data = { + init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent + }; + + library.images[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildImage( data ) { + + if ( data.build !== undefined ) return data.build; + + return data.init_from; + + } + + function getImage( id ) { + + return getBuild( library.images[ id ], buildImage ); + + } + + // effect + + function parseEffect( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + data.profile = parseEffectProfileCOMMON( child ); + break; + + } + + } + + library.effects[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseEffectProfileCOMMON( xml ) { + + var data = { + surfaces: {}, + samplers: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'newparam': + parseEffectNewparam( child, data ); + break; + + case 'technique': + data.technique = parseEffectTechnique( child ); + break; + + case 'extra': + data.extra = parseEffectExtra( child ); + break; + + } + + } + + return data; + + } + + function parseEffectNewparam( xml, data ) { + + var sid = xml.getAttribute( 'sid' ); + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'surface': + data.surfaces[ sid ] = parseEffectSurface( child ); + break; + + case 'sampler2D': + data.samplers[ sid ] = parseEffectSampler( child ); + break; + + } + + } + + } + + function parseEffectSurface( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'init_from': + data.init_from = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectSampler( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + data.source = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectTechnique( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + data.type = child.nodeName; + data.parameters = parseEffectParameters( child ); + break; + + } + + } + + return data; + + } + + function parseEffectParameters( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'emission': + case 'diffuse': + case 'specular': + case 'shininess': + case 'transparency': + data[ child.nodeName ] = parseEffectParameter( child ); + break; + case 'transparent': + data[ child.nodeName ] = { + opaque: child.getAttribute( 'opaque' ), + data: parseEffectParameter( child ) + }; + break; + + } + + } + + return data; + + } + + function parseEffectParameter( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + data[ child.nodeName ] = parseFloats( child.textContent ); + break; + + case 'float': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'texture': + data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) }; + break; + + } + + } + + return data; + + } + + function parseEffectParameterTexture( xml ) { + + var data = { + technique: {} + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'extra': + parseEffectParameterTextureExtra( child, data ); + break; + + } + + } + + return data; + + } + + function parseEffectParameterTextureExtra( xml, data ) { + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + parseEffectParameterTextureExtraTechnique( child, data ); + break; + + } + + } + + } + + function parseEffectParameterTextureExtraTechnique( xml, data ) { + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'repeatU': + case 'repeatV': + case 'offsetU': + case 'offsetV': + data.technique[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'wrapU': + case 'wrapV': + + // some files have values for wrapU/wrapV which become NaN via parseInt + + if ( child.textContent.toUpperCase() === 'TRUE' ) { + + data.technique[ child.nodeName ] = 1; + + } else if ( child.textContent.toUpperCase() === 'FALSE' ) { + + data.technique[ child.nodeName ] = 0; + + } else { + + data.technique[ child.nodeName ] = parseInt( child.textContent ); + + } + + break; + + } + + } + + } + + function parseEffectExtra( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + data.technique = parseEffectExtraTechnique( child ); + break; + + } + + } + + return data; + + } + + function parseEffectExtraTechnique( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'double_sided': + data[ child.nodeName ] = parseInt( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildEffect( data ) { + + return data; + + } + + function getEffect( id ) { + + return getBuild( library.effects[ id ], buildEffect ); + + } + + // material + + function parseMaterial( xml ) { + + var data = { + name: xml.getAttribute( 'name' ) + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_effect': + data.url = parseId( child.getAttribute( 'url' ) ); + break; + + } + + } + + library.materials[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildMaterial( data ) { + + var effect = getEffect( data.url ); + var technique = effect.profile.technique; + var extra = effect.profile.extra; + + var material; + + switch ( technique.type ) { + + case 'phong': + case 'blinn': + material = new THREE.MeshPhongMaterial(); + break; + + case 'lambert': + material = new THREE.MeshLambertMaterial(); + break; + + default: + material = new THREE.MeshBasicMaterial(); + break; + + } + + material.name = data.name; + + function getTexture( textureObject ) { + + var sampler = effect.profile.samplers[ textureObject.id ]; + var image; + + // get image + + if ( sampler !== undefined ) { + + var surface = effect.profile.surfaces[ sampler.source ]; + image = getImage( surface.init_from ); + + } else { + + console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' ); + image = getImage( textureObject.id ); + + } + + // create texture if image is avaiable + + if ( image !== undefined ) { + + var texture = textureLoader.load( image ); + + var extra = textureObject.extra; + + if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) { + + var technique = extra.technique; + + texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + + texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 ); + texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 ); + + } else { + + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + + } + + return texture; + + } else { + + console.error( 'THREE.ColladaLoader: Unable to load texture with ID:', textureObject.id ); + + return null; + + } + + } + + var parameters = technique.parameters; + + for ( var key in parameters ) { + + var parameter = parameters[ key ]; + + switch ( key ) { + + case 'diffuse': + if ( parameter.color ) material.color.fromArray( parameter.color ); + if ( parameter.texture ) material.map = getTexture( parameter.texture ); + break; + case 'specular': + if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color ); + if ( parameter.texture ) material.specularMap = getTexture( parameter.texture ); + break; + case 'shininess': + if ( parameter.float && material.shininess ) + material.shininess = parameter.float; + break; + case 'emission': + if ( parameter.color && material.emissive ) + material.emissive.fromArray( parameter.color ); + break; + + } + + } + + // + + var transparent = parameters[ 'transparent' ]; + var transparency = parameters[ 'transparency' ]; + + // does not exist but + + if ( transparency === undefined && transparent ) { + + transparency = { + float: 1 + }; + + } + + // does not exist but + + if ( transparent === undefined && transparency ) { + + transparent = { + opaque: 'A_ONE', + data: { + color: [ 1, 1, 1, 1 ] + } }; + + } + + if ( transparent && transparency ) { + + // handle case if a texture exists but no color + + if ( transparent.data.texture ) { + + material.alphaMap = getTexture( transparent.data.texture ); + material.transparent = true; + + } else { + + var color = transparent.data.color; + + switch ( transparent.opaque ) { + + case 'A_ONE': + material.opacity = color[ 3 ] * transparency.float; + break; + case 'RGB_ZERO': + material.opacity = 1 - ( color[ 0 ] * transparency.float ); + break; + case 'A_ZERO': + material.opacity = 1 - ( color[ 3 ] * transparency.float ); + break; + case 'RGB_ONE': + material.opacity = color[ 0 ] * transparency.float; + break; + default: + console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque ); + + } + + if ( material.opacity < 1 ) material.transparent = true; + + } + + } + + // + + if ( extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1 ) { + + material.side = THREE.DoubleSide; + + } + + return material; + + } + + function getMaterial( id ) { + + return getBuild( library.materials[ id ], buildMaterial ); + + } + + // camera + + function parseCamera( xml ) { + + var data = { + name: xml.getAttribute( 'name' ) + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'optics': + data.optics = parseCameraOptics( child ); + break; + + } + + } + + library.cameras[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseCameraOptics( xml ) { + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'technique_common': + return parseCameraTechnique( child ); + + } + + } + + return {}; + + } + + function parseCameraTechnique( xml ) { + + var data = {}; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'perspective': + case 'orthographic': + + data.technique = child.nodeName; + data.parameters = parseCameraParameters( child ); + + break; + + } + + } + + return data; + + } + + function parseCameraParameters( xml ) { + + var data = {}; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'xfov': + case 'yfov': + case 'xmag': + case 'ymag': + case 'znear': + case 'zfar': + case 'aspect_ratio': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildCamera( data ) { + + var camera; + + switch ( data.optics.technique ) { + + case 'perspective': + camera = new THREE.PerspectiveCamera( + data.optics.parameters.yfov, + data.optics.parameters.aspect_ratio, + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + case 'orthographic': + var ymag = data.optics.parameters.ymag; + var xmag = data.optics.parameters.xmag; + var aspectRatio = data.optics.parameters.aspect_ratio; + + xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag; + ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag; + + xmag *= 0.5; + ymag *= 0.5; + + camera = new THREE.OrthographicCamera( + - xmag, xmag, ymag, - ymag, // left, right, top, bottom + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + default: + camera = new THREE.PerspectiveCamera(); + break; + + } + + camera.name = data.name; + + return camera; + + } + + function getCamera( id ) { + + var data = library.cameras[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildCamera ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id ); + + return null; + + } + + // light + + function parseLight( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + data = parseLightTechnique( child ); + break; + + } + + } + + library.lights[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseLightTechnique( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'directional': + case 'point': + case 'spot': + case 'ambient': + + data.technique = child.nodeName; + data.parameters = parseLightParameters( child ); + + } + + } + + return data; + + } + + function parseLightParameters( xml ) { + + var data = {}; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + var array = parseFloats( child.textContent ); + data.color = new THREE.Color().fromArray( array ); + break; + + case 'falloff_angle': + data.falloffAngle = parseFloat( child.textContent ); + break; + + case 'quadratic_attenuation': + var f = parseFloat( child.textContent ); + data.distance = f ? Math.sqrt( 1 / f ) : 0; + break; + + } + + } + + return data; + + } + + function buildLight( data ) { + + var light; + + switch ( data.technique ) { + + case 'directional': + light = new THREE.DirectionalLight(); + break; + + case 'point': + light = new THREE.PointLight(); + break; + + case 'spot': + light = new THREE.SpotLight(); + break; + + case 'ambient': + light = new THREE.AmbientLight(); + break; + + } + + if ( data.parameters.color ) light.color.copy( data.parameters.color ); + if ( data.parameters.distance ) light.distance = data.parameters.distance; + + return light; + + } + + function getLight( id ) { + + var data = library.lights[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildLight ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id ); + + return null; + + } + + // geometry + + function parseGeometry( xml ) { + + var data = { + name: xml.getAttribute( 'name' ), + sources: {}, + vertices: {}, + primitives: [] + }; + + var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ]; + + // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep + if ( mesh === undefined ) return; + + for ( var i = 0; i < mesh.childNodes.length; i ++ ) { + + var child = mesh.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + var id = child.getAttribute( 'id' ); + + switch ( child.nodeName ) { + + case 'source': + data.sources[ id ] = parseSource( child ); + break; + + case 'vertices': + // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; + data.vertices = parseGeometryVertices( child ); + break; + + case 'polygons': + console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName ); + break; + + case 'lines': + case 'linestrips': + case 'polylist': + case 'triangles': + data.primitives.push( parseGeometryPrimitive( child ) ); + break; + + default: + console.log( child ); + + } + + } + + library.geometries[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSource( xml ) { + + var data = { + array: [], + stride: 3 + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'float_array': + data.array = parseFloats( child.textContent ); + break; + + case 'Name_array': + data.array = parseStrings( child.textContent ); + break; + + case 'technique_common': + var accessor = getElementsByTagName( child, 'accessor' )[ 0 ]; + + if ( accessor !== undefined ) { + + data.stride = parseInt( accessor.getAttribute( 'stride' ) ); + + } + break; + + } + + } + + return data; + + } + + function parseGeometryVertices( xml ) { + + var data = {}; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) ); + + } + + return data; + + } + + function parseGeometryPrimitive( xml ) { + + var primitive = { + type: xml.nodeName, + material: xml.getAttribute( 'material' ), + count: parseInt( xml.getAttribute( 'count' ) ), + inputs: {}, + stride: 0 + }; + + for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + var id = parseId( child.getAttribute( 'source' ) ); + var semantic = child.getAttribute( 'semantic' ); + var offset = parseInt( child.getAttribute( 'offset' ) ); + primitive.inputs[ semantic ] = { id: id, offset: offset }; + primitive.stride = Math.max( primitive.stride, offset + 1 ); + break; + + case 'vcount': + primitive.vcount = parseInts( child.textContent ); + break; + + case 'p': + primitive.p = parseInts( child.textContent ); + break; + + } + + } + + return primitive; + + } + + function groupPrimitives( primitives ) { + + var build = {}; + + for ( var i = 0; i < primitives.length; i ++ ) { + + var primitive = primitives[ i ]; + + if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = []; + + build[ primitive.type ].push( primitive ); + + } + + return build; + + } + + function buildGeometry( data ) { + + var build = {}; + + var sources = data.sources; + var vertices = data.vertices; + var primitives = data.primitives; + + if ( primitives.length === 0 ) return {}; + + // our goal is to create one buffer geoemtry for a single type of primitives + // first, we group all primitives by their type + + var groupedPrimitives = groupPrimitives( primitives ); + + for ( var type in groupedPrimitives ) { + + // second, we create for each type of primitives (polylist,triangles or lines) a buffer geometry + + build[ type ] = buildGeometryType( groupedPrimitives[ type ], sources, vertices ); + + } + + return build; + + } + + function buildGeometryType( primitives, sources, vertices ) { + + var build = {}; + + var position = { array: [], stride: 0 }; + var normal = { array: [], stride: 0 }; + var uv = { array: [], stride: 0 }; + var color = { array: [], stride: 0 }; + + var skinIndex = { array: [], stride: 4 }; + var skinWeight = { array: [], stride: 4 }; + + var geometry = new THREE.BufferGeometry(); + + var materialKeys = []; + + var start = 0, count = 0; + + for ( var p = 0; p < primitives.length; p ++ ) { + + var primitive = primitives[ p ]; + var inputs = primitive.inputs; + var triangleCount = 1; + + if ( primitive.vcount && primitive.vcount[ 0 ] === 4 ) { + + triangleCount = 2; // one quad -> two triangles + + } + + // groups + + if ( primitive.type === 'lines' || primitive.type === 'linestrips' ) { + + count = primitive.count * 2; + + } else { + + count = primitive.count * 3 * triangleCount; + + } + + geometry.addGroup( start, count, p ); + start += count; + + // material + + if ( primitive.material ) { + + materialKeys.push( primitive.material ); + + } + + // geometry data + + for ( var name in inputs ) { + + var input = inputs[ name ]; + + switch ( name ) { + + case 'VERTEX': + for ( var key in vertices ) { + + var id = vertices[ key ]; + + switch ( key ) { + + case 'POSITION': + buildGeometryData( primitive, sources[ id ], input.offset, position.array ); + position.stride = sources[ id ].stride; + + if ( sources.skinWeights && sources.skinIndices ) { + + buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array ); + buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array ); + + } + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ id ], input.offset, normal.array ); + normal.stride = sources[ id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ id ], input.offset, color.array ); + color.stride = sources[ id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ id ], input.offset, uv.array ); + uv.stride = sources[ id ].stride; + break; + + default: + console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key ); + + } + + } + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array ); + normal.stride = sources[ input.id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ input.id ], input.offset, color.array ); + color.stride = sources[ input.id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array ); + uv.stride = sources[ input.id ].stride; + break; + + } + + } + + } + + // build geometry + + if ( position.array.length > 0 ) geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( position.array, position.stride ) ); + if ( normal.array.length > 0 ) geometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normal.array, normal.stride ) ); + if ( color.array.length > 0 ) geometry.addAttribute( 'color', new THREE.Float32BufferAttribute( color.array, color.stride ) ); + if ( uv.array.length > 0 ) geometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( uv.array, uv.stride ) ); + + if ( skinIndex.array.length > 0 ) geometry.addAttribute( 'skinIndex', new THREE.Float32BufferAttribute( skinIndex.array, skinIndex.stride ) ); + if ( skinWeight.array.length > 0 ) geometry.addAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeight.array, skinWeight.stride ) ); + + build.data = geometry; + build.type = primitives[ 0 ].type; + build.materialKeys = materialKeys; + + return build; + + } + + function buildGeometryData( primitive, source, offset, array ) { + + var indices = primitive.p; + var stride = primitive.stride; + var vcount = primitive.vcount; + + function pushVector( i ) { + + var index = indices[ i + offset ] * sourceStride; + var length = index + sourceStride; + + for ( ; index < length; index ++ ) { + + array.push( sourceArray[ index ] ); + + } + + } + + var maxcount = 0; + + var sourceArray = source.array; + var sourceStride = source.stride; + + if ( primitive.vcount !== undefined ) { + + var index = 0; + + for ( var i = 0, l = vcount.length; i < l; i ++ ) { + + var count = vcount[ i ]; + + if ( count === 4 ) { + + var a = index + stride * 0; + var b = index + stride * 1; + var c = index + stride * 2; + var d = index + stride * 3; + + pushVector( a ); pushVector( b ); pushVector( d ); + pushVector( b ); pushVector( c ); pushVector( d ); + + } else if ( count === 3 ) { + + var a = index + stride * 0; + var b = index + stride * 1; + var c = index + stride * 2; + + pushVector( a ); pushVector( b ); pushVector( c ); + + } else { + + maxcount = Math.max( maxcount, count ); + + } + + index += stride * count; + + } + + if ( maxcount > 0 ) { + + console.log( 'THREE.ColladaLoader: Geometry has faces with more than 4 vertices.' ); + + } + + } else { + + for ( var i = 0, l = indices.length; i < l; i += stride ) { + + pushVector( i ); + + } + + } + + } + + function getGeometry( id ) { + + return getBuild( library.geometries[ id ], buildGeometry ); + + } + + // kinematics + + function parseKinematicsModel( xml ) { + + var data = { + name: xml.getAttribute( 'name' ) || '', + joints: {}, + links: [] + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + parseKinematicsTechniqueCommon( child, data ); + break; + + } + + } + + library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildKinematicsModel( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsModel( id ) { + + return getBuild( library.kinematicsModels[ id ], buildKinematicsModel ); + + } + + function parseKinematicsTechniqueCommon( xml, data ) { + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'joint': + data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child ); + break; + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + } + + } + + } + + function parseKinematicsJoint( xml ) { + + var data; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'prismatic': + case 'revolute': + data = parseKinematicsJointParameter( child ); + break; + + } + + } + + return data; + + } + + function parseKinematicsJointParameter( xml, data ) { + + var data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + axis: new THREE.Vector3(), + limits: { + min: 0, + max: 0 + }, + type: xml.nodeName, + static: false, + zeroPosition: 0, + middlePosition: 0 + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + var array = parseFloats( child.textContent ); + data.axis.fromArray( array ); + break; + case 'limits': + var max = child.getElementsByTagName( 'max' )[ 0 ]; + var min = child.getElementsByTagName( 'min' )[ 0 ]; + + data.limits.max = parseFloat( max.textContent ); + data.limits.min = parseFloat( min.textContent ); + break; + + } + + } + + // if min is equal to or greater than max, consider the joint static + + if ( data.limits.min >= data.limits.max ) { + + data.static = true; + + } + + // calculate middle position + + data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0; + + return data; + + } + + function parseKinematicsLink( xml ) { + + var data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + attachments: [], + transforms: [] + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'attachment_full': + data.attachments.push( parseKinematicsAttachment( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsAttachment( xml ) { + + var data = { + joint: xml.getAttribute( 'joint' ).split( '/' ).pop(), + transforms: [], + links: [] + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsTransform( xml ) { + + var data = { + type: xml.nodeName + }; + + var array = parseFloats( xml.textContent ); + + switch ( data.type ) { + + case 'matrix': + data.obj = new THREE.Matrix4(); + data.obj.fromArray( array ).transpose(); + break; + + case 'translate': + data.obj = new THREE.Vector3(); + data.obj.fromArray( array ); + break; + + case 'rotate': + data.obj = new THREE.Vector3(); + data.obj.fromArray( array ); + data.angle = THREE.Math.degToRad( array[ 3 ] ); + break; + + } + + return data; + + } + + function parseKinematicsScene( xml ) { + + var data = { + bindJointAxis: [] + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_joint_axis': + data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) ); + break; + + } + + } + + library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data; + + } + + function parseKinematicsBindJointAxis( xml ) { + + var data = { + target: xml.getAttribute( 'target' ).split( '/' ).pop() + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + var param = child.getElementsByTagName( 'param' )[ 0 ]; + data.axis = param.textContent; + var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ]; + data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 ); + break; + + } + + } + + return data; + + } + + function buildKinematicsScene( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsScene( id ) { + + return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene ); + + } + + function setupKinematics() { + + var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ]; + var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ]; + var visualSceneId = Object.keys( library.visualScenes )[ 0 ]; + + if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return; + + var kinematicsModel = getKinematicsModel( kinematicsModelId ); + var kinematicsScene = getKinematicsScene( kinematicsSceneId ); + var visualScene = getVisualScene( visualSceneId ); + + var bindJointAxis = kinematicsScene.bindJointAxis; + var jointMap = {}; + + for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) { + + var axis = bindJointAxis[ i ]; + + // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' + + var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' ); + + if ( targetElement ) { + + // get the parent of the transfrom element + + var parentVisualElement = targetElement.parentElement; + + // connect the joint of the kinematics model with the element in the visual scene + + connect( axis.jointIndex, parentVisualElement ); + + } + + } + + function connect( jointIndex, visualElement ) { + + var visualElementName = visualElement.getAttribute( 'name' ); + var joint = kinematicsModel.joints[ jointIndex ]; + + visualScene.traverse( function ( object ) { + + if ( object.name === visualElementName ) { + + jointMap[ jointIndex ] = { + object: object, + transforms: buildTransformList( visualElement ), + joint: joint, + position: joint.zeroPosition + }; + + } + + } ); + + } + + var m0 = new THREE.Matrix4(); + + kinematics = { + + joints: kinematicsModel && kinematicsModel.joints, + + getJointValue: function ( jointIndex ) { + + var jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + return jointData.position; + + } else { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' ); + + } + + }, + + setJointValue: function ( jointIndex, value ) { + + var jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + var joint = jointData.joint; + + if ( value > joint.limits.max || value < joint.limits.min ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' ); + + } else if ( joint.static ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' ); + + } else { + + var object = jointData.object; + var axis = joint.axis; + var transforms = jointData.transforms; + + matrix.identity(); + + // each update, we have to apply all transforms in the correct order + + for ( var i = 0; i < transforms.length; i ++ ) { + + var transform = transforms[ i ]; + + // if there is a connection of the transform node with a joint, apply the joint value + + if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) { + + switch ( joint.type ) { + + case 'revolute': + matrix.multiply( m0.makeRotationAxis( axis, THREE.Math.degToRad( value ) ) ); + break; + + case 'prismatic': + matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) ); + break; + + default: + console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type ); + break; + + } + + } else { + + switch ( transform.type ) { + + case 'matrix': + matrix.multiply( transform.obj ); + break; + + case 'translate': + matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); + break; + + case 'scale': + matrix.scale( transform.obj ); + break; + + case 'rotate': + matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) ); + break; + + } + + } + + } + + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + jointMap[ jointIndex ].position = value; + + } + + } else { + + console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' ); + + } + + } + + }; + + } + + function buildTransformList( node ) { + + var transforms = []; + + var xml = collada.querySelector( '[id="' + node.id + '"]' ); + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'matrix': + var array = parseFloats( child.textContent ); + var matrix = new THREE.Matrix4().fromArray( array ).transpose(); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: matrix + } ); + break; + + case 'translate': + case 'scale': + var array = parseFloats( child.textContent ); + var vector = new THREE.Vector3().fromArray( array ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector + } ); + break; + + case 'rotate': + var array = parseFloats( child.textContent ); + var vector = new THREE.Vector3().fromArray( array ); + var angle = THREE.Math.degToRad( array[ 3 ] ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector, + angle: angle + } ); + break; + + } + + } + + return transforms; + + } + + // nodes + + function prepareNodes( xml ) { + + var elements = xml.getElementsByTagName( 'node' ); + + // ensure all node elements have id attributes + + for ( var i = 0; i < elements.length; i ++ ) { + + var element = elements[ i ]; + + if ( element.hasAttribute( 'id' ) === false ) { + + element.setAttribute( 'id', generateId() ); + + } + + } + + } + + var matrix = new THREE.Matrix4(); + var vector = new THREE.Vector3(); + + function parseNode( xml ) { + + var data = { + name: xml.getAttribute( 'name' ) || '', + type: xml.getAttribute( 'type' ), + id: xml.getAttribute( 'id' ), + sid: xml.getAttribute( 'sid' ), + matrix: new THREE.Matrix4(), + nodes: [], + instanceCameras: [], + instanceControllers: [], + instanceLights: [], + instanceGeometries: [], + instanceNodes: [], + transforms: {} + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'node': + data.nodes.push( child.getAttribute( 'id' ) ); + parseNode( child ); + break; + + case 'instance_camera': + data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_controller': + data.instanceControllers.push( parseNodeInstance( child ) ); + break; + + case 'instance_light': + data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_geometry': + data.instanceGeometries.push( parseNodeInstance( child ) ); + break; + + case 'instance_node': + data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'matrix': + var array = parseFloats( child.textContent ); + data.matrix.multiply( matrix.fromArray( array ).transpose() ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'translate': + var array = parseFloats( child.textContent ); + vector.fromArray( array ); + data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'rotate': + var array = parseFloats( child.textContent ); + var angle = THREE.Math.degToRad( array[ 3 ] ); + data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'scale': + var array = parseFloats( child.textContent ); + data.matrix.scale( vector.fromArray( array ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'extra': + break; + + default: + console.log( child ); + + } + + } + + library.nodes[ data.id ] = data; + + return data; + + } + + function parseNodeInstance( xml ) { + + var data = { + id: parseId( xml.getAttribute( 'url' ) ), + materials: {}, + skeletons: [] + }; + + for ( var i = 0; i < xml.childNodes.length; i ++ ) { + + var child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'bind_material': + var instances = child.getElementsByTagName( 'instance_material' ); + + for ( var j = 0; j < instances.length; j ++ ) { + + var instance = instances[ j ]; + var symbol = instance.getAttribute( 'symbol' ); + var target = instance.getAttribute( 'target' ); + + data.materials[ symbol ] = parseId( target ); + + } + + break; + + case 'skeleton': + data.skeletons.push( parseId( child.textContent ) ); + break; + + default: + break; + + } + + } + + return data; + + } + + function buildSkeleton( skeletons, joints ) { + + var boneData = []; + var sortedBoneData = []; + + var i, j, data; + + // a skeleton can have multiple root bones. collada expresses this + // situtation with multiple "skeleton" tags per controller instance + + for ( i = 0; i < skeletons.length; i ++ ) { + + var skeleton = skeletons[ i ]; + var root = getNode( skeleton ); + + // setup bone data for a single bone hierarchy + + buildBoneHierarchy( root, joints, boneData ); + + } + + // sort bone data (the order is defined in the corresponding controller) + + for ( i = 0; i < joints.length; i ++ ) { + + for ( j = 0; j < boneData.length; j ++ ) { + + data = boneData[ j ]; + + if ( data.bone.name === joints[ i ].name ) { + + sortedBoneData[ i ] = data; + data.processed = true; + break; + + } + + } + + } + + // add unprocessed bone data at the end of the list + + for ( i = 0; i < boneData.length; i ++ ) { + + data = boneData[ i ]; + + if ( data.processed === false ) { + + sortedBoneData.push( data ); + data.processed = true; + + } + + } + + // setup arrays for skeleton creation + + var bones = []; + var boneInverses = []; + + for ( i = 0; i < sortedBoneData.length; i ++ ) { + + data = sortedBoneData[ i ]; + + bones.push( data.bone ); + boneInverses.push( data.boneInverse ); + + } + + return new THREE.Skeleton( bones, boneInverses ); + + } + + function buildBoneHierarchy( root, joints, boneData ) { + + // setup bone data from visual scene + + root.traverse( function ( object ) { + + if ( object.isBone === true ) { + + var boneInverse; + + // retrieve the boneInverse from the controller data + + for ( var i = 0; i < joints.length; i ++ ) { + + var joint = joints[ i ]; + + if ( joint.name === object.name ) { + + boneInverse = joint.boneInverse; + break; + + } + + } + + if ( boneInverse === undefined ) { + + // Unfortunately, there can be joints in the visual scene that are not part of the + // corresponding controller. In this case, we have to create a dummy boneInverse matrix + // for the respective bone. This bone won't affect any vertices, because there are no skin indices + // and weights defined for it. But we still have to add the bone to the sorted bone list in order to + // ensure a correct animation of the model. + + boneInverse = new THREE.Matrix4(); + + } + + boneData.push( { bone: object, boneInverse: boneInverse, processed: false } ); + + } + + } ); + + } + + function buildNode( data ) { + + var objects = []; + + var matrix = data.matrix; + var nodes = data.nodes; + var type = data.type; + var instanceCameras = data.instanceCameras; + var instanceControllers = data.instanceControllers; + var instanceLights = data.instanceLights; + var instanceGeometries = data.instanceGeometries; + var instanceNodes = data.instanceNodes; + + // nodes + + for ( var i = 0, l = nodes.length; i < l; i ++ ) { + + objects.push( getNode( nodes[ i ] ) ); + + } + + // instance cameras + + for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) { + + var instanceCamera = getCamera( instanceCameras[ i ] ); + + if ( instanceCamera !== null ) { + + objects.push( instanceCamera.clone() ); + + } + + } + + // instance controllers + + for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) { + + var instance = instanceControllers[ i ]; + var controller = getController( instance.id ); + var geometries = getGeometry( controller.id ); + var newObjects = buildObjects( geometries, instance.materials ); + + var skeletons = instance.skeletons; + var joints = controller.skin.joints; + + var skeleton = buildSkeleton( skeletons, joints ); + + for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) { + + var object = newObjects[ j ]; + + if ( object.isSkinnedMesh ) { + + object.bind( skeleton, controller.skin.bindMatrix ); + object.normalizeSkinWeights(); + + } + + objects.push( object ); + + } + + } + + // instance lights + + for ( var i = 0, l = instanceLights.length; i < l; i ++ ) { + + var instanceLight = getLight( instanceLights[ i ] ); + + if ( instanceLight !== null ) { + + objects.push( instanceLight.clone() ); + + } + + } + + // instance geometries + + for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) { + + var instance = instanceGeometries[ i ]; + + // a single geometry instance in collada can lead to multiple object3Ds. + // this is the case when primitives are combined like triangles and lines + + var geometries = getGeometry( instance.id ); + var newObjects = buildObjects( geometries, instance.materials ); + + for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) { + + objects.push( newObjects[ j ] ); + + } + + } + + // instance nodes + + for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) { + + objects.push( getNode( instanceNodes[ i ] ).clone() ); + + } + + var object; + + if ( nodes.length === 0 && objects.length === 1 ) { + + object = objects[ 0 ]; + + } else { + + object = ( type === 'JOINT' ) ? new THREE.Bone() : new THREE.Group(); + + for ( var i = 0; i < objects.length; i ++ ) { + + object.add( objects[ i ] ); + + } + + } + + if ( object.name === '' ) { + + object.name = ( type === 'JOINT' ) ? data.sid : data.name; + + } + + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + return object; + + } + + function resolveMaterialBinding( keys, instanceMaterials ) { + + var materials = []; + + for ( var i = 0, l = keys.length; i < l; i ++ ) { + + var id = instanceMaterials[ keys[ i ] ]; + materials.push( getMaterial( id ) ); + + } + + return materials; + + } + + function buildObjects( geometries, instanceMaterials ) { + + var objects = []; + + for ( var type in geometries ) { + + var geometry = geometries[ type ]; + + var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials ); + + // handle case if no materials are defined + + if ( materials.length === 0 ) { + + if ( type === 'lines' || type === 'linestrips' ) { + + materials.push( new THREE.LineBasicMaterial() ); + + } else { + + materials.push( new THREE.MeshPhongMaterial() ); + + } + + } + + // regard skinning + + var skinning = ( geometry.data.attributes.skinIndex !== undefined ); + + if ( skinning ) { + + for ( var i = 0, l = materials.length; i < l; i ++ ) { + + materials[ i ].skinning = true; + + } + + } + + // choose between a single or multi materials (material array) + + var material = ( materials.length === 1 ) ? materials[ 0 ] : materials; + + // now create a specific 3D object + + var object; + + switch ( type ) { + + case 'lines': + object = new THREE.LineSegments( geometry.data, material ); + break; + + case 'linestrips': + object = new THREE.Line( geometry.data, material ); + break; + + case 'triangles': + case 'polylist': + if ( skinning ) { + + object = new THREE.SkinnedMesh( geometry.data, material ); + + } else { + + object = new THREE.Mesh( geometry.data, material ); + + } + break; + + } + + objects.push( object ); + + } + + return objects; + + } + + function getNode( id ) { + + return getBuild( library.nodes[ id ], buildNode ); + + } + + // visual scenes + + function parseVisualScene( xml ) { + + var data = { + name: xml.getAttribute( 'name' ), + children: [] + }; + + prepareNodes( xml ); + + var elements = getElementsByTagName( xml, 'node' ); + + for ( var i = 0; i < elements.length; i ++ ) { + + data.children.push( parseNode( elements[ i ] ) ); + + } + + library.visualScenes[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildVisualScene( data ) { + + var group = new THREE.Group(); + group.name = data.name; + + var children = data.children; + + for ( var i = 0; i < children.length; i ++ ) { + + var child = children[ i ]; + + group.add( getNode( child.id ) ); + + } + + return group; + + } + + function getVisualScene( id ) { + + return getBuild( library.visualScenes[ id ], buildVisualScene ); + + } + + // scenes + + function parseScene( xml ) { + + var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ]; + return getVisualScene( parseId( instance.getAttribute( 'url' ) ) ); + + } + + function setupAnimations() { + + var clips = library.clips; + + if ( isEmpty( clips ) === true ) { + + if ( isEmpty( library.animations ) === false ) { + + // if there are animations but no clips, we create a default clip for playback + + var tracks = []; + + for ( var id in library.animations ) { + + var animationTracks = getAnimation( id ); + + for ( var i = 0, l = animationTracks.length; i < l; i ++ ) { + + tracks.push( animationTracks[ i ] ); + + } + + } + + animations.push( new THREE.AnimationClip( 'default', - 1, tracks ) ); + + } + + } else { + + for ( var id in clips ) { + + animations.push( getAnimationClip( id ) ); + + } + + } + + } + + console.time( 'THREE.ColladaLoader' ); + + if ( text.length === 0 ) { + + return { scene: new THREE.Scene() }; + + } + + console.time( 'THREE.ColladaLoader: DOMParser' ); + + var xml = new DOMParser().parseFromString( text, 'application/xml' ); + + console.timeEnd( 'THREE.ColladaLoader: DOMParser' ); + + var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ]; + + // metadata + + var version = collada.getAttribute( 'version' ); + //console.log( 'THREE.ColladaLoader: File version', version ); + + var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] ); + var textureLoader = new THREE.TextureLoader( this.manager ); + textureLoader.setPath( path ).setCrossOrigin( this.crossOrigin ); + + // + + var animations = []; + var kinematics = {}; + var count = 0; + + // + + var library = { + animations: {}, + clips: {}, + controllers: {}, + images: {}, + effects: {}, + materials: {}, + cameras: {}, + lights: {}, + geometries: {}, + nodes: {}, + visualScenes: {}, + kinematicsModels: {}, + kinematicsScenes: {} + }; + + console.time( 'THREE.ColladaLoader: Parse' ); + + parseLibrary( collada, 'library_animations', 'animation', parseAnimation ); + parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip ); + parseLibrary( collada, 'library_controllers', 'controller', parseController ); + parseLibrary( collada, 'library_images', 'image', parseImage ); + parseLibrary( collada, 'library_effects', 'effect', parseEffect ); + parseLibrary( collada, 'library_materials', 'material', parseMaterial ); + parseLibrary( collada, 'library_cameras', 'camera', parseCamera ); + parseLibrary( collada, 'library_lights', 'light', parseLight ); + parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry ); + parseLibrary( collada, 'library_nodes', 'node', parseNode ); + parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene ); + parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel ); + parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene ); + + console.timeEnd( 'THREE.ColladaLoader: Parse' ); + + console.time( 'THREE.ColladaLoader: Build' ); + + buildLibrary( library.animations, buildAnimation ); + buildLibrary( library.clips, buildAnimationClip ); + buildLibrary( library.controllers, buildController ); + buildLibrary( library.images, buildImage ); + buildLibrary( library.effects, buildEffect ); + buildLibrary( library.materials, buildMaterial ); + buildLibrary( library.cameras, buildCamera ); + buildLibrary( library.lights, buildLight ); + buildLibrary( library.geometries, buildGeometry ); + buildLibrary( library.visualScenes, buildVisualScene ); + + console.timeEnd( 'THREE.ColladaLoader: Build' ); + + setupAnimations(); + setupKinematics(); + + var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] ); + + if ( asset.upAxis === 'Z_UP' ) { + + scene.rotation.x = - Math.PI / 2; + + } + + scene.scale.multiplyScalar( asset.unit ); + + console.timeEnd( 'THREE.ColladaLoader' ); + + return { + animations: animations, + kinematics: kinematics, + library: library, + scene: scene + }; + + } + +}; diff --git a/static/exercises/visual_odometry_3D/3DScene/OBJLoader.js b/static/exercises/visual_odometry_3D/3DScene/OBJLoader.js new file mode 100644 index 000000000..15b1f90bf --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/OBJLoader.js @@ -0,0 +1,793 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.OBJLoader = ( function () { + + // o object_name | g group_name + var object_pattern = /^[og]\s*(.+)?/; + // mtllib file_reference + var material_library_pattern = /^mtllib /; + // usemtl material_name + var material_use_pattern = /^usemtl /; + + function ParserState() { + + var state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [] + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + var previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + var material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + var cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + var lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + var declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + var index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + var src = this.vertices; + var dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + var src = this.normals; + var dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addColor: function ( a, b, c ) { + + var src = this.colors; + var dst = this.object.geometry.colors; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addUVLine: function ( a ) { + + var src = this.uvs; + var dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + var vLen = this.vertices.length; + + var ia = this.parseVertexIndex( a, vLen ); + var ib = this.parseVertexIndex( b, vLen ); + var ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + + if ( ua !== undefined && ua !== '' ) { + + var uvLen = this.uvs.length; + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + this.addUV( ia, ib, ic ); + + } + + if ( na !== undefined && na !== '' ) { + + // Normals are many times the same. If so, skip function call and parseInt. + var nLen = this.normals.length; + ia = this.parseNormalIndex( na, nLen ); + + ib = na === nb ? ia : this.parseNormalIndex( nb, nLen ); + ic = na === nc ? ia : this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } + + if ( this.colors.length > 0 ) { + + this.addColor( ia, ib, ic ); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + var vLen = this.vertices.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + var vLen = this.vertices.length; + var uvLen = this.uvs.length; + + for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + + } + + // + + function OBJLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; + + this.materials = null; + + } + + OBJLoader.prototype = { + + constructor: OBJLoader, + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new THREE.FileLoader( scope.manager ); + loader.setPath( this.path ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( text ) ); + + }, onProgress, onError ); + + }, + + setPath: function ( value ) { + + this.path = value; + + return this; + + }, + + setMaterials: function ( materials ) { + + this.materials = materials; + + return this; + + }, + + parse: function ( text ) { + + console.time( 'OBJLoader' ); + + var state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + var lines = text.split( '\n' ); + var line = '', lineFirstChar = ''; + var lineLength = 0; + var result = []; + + // Faster to just trim left side of the line. Use if available. + var trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( var i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + var data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length === 8 ) { + + state.colors.push( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + + ); + + } + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + var lineData = line.substr( 1 ).trim(); + var vertexData = lineData.split( /\s+/ ); + var faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) { + + var vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + var vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + var v1 = faceVertices[ 0 ]; + + for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + var v2 = faceVertices[ j ]; + var v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + var lineParts = line.substring( 1 ).trim().split( " " ); + var lineVertices = [], lineUVs = []; + + if ( line.indexOf( "/" ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) { + + var parts = lineParts[ li ].split( "/" ); + + if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] ); + + } + + } + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + var lineData = line.substr( 1 ).trim(); + var pointData = lineData.split( " " ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // var name = result[ 0 ].substr( 1 ).trim(); + var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 ); + + state.startObject( name ); + + } else if ( material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * or + * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + var value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + var material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + var container = new THREE.Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + for ( var i = 0, l = state.objects.length; i < l; i ++ ) { + + var object = state.objects[ i ]; + var geometry = object.geometry; + var materials = object.materials; + var isLine = ( geometry.type === 'Line' ); + var isPoints = ( geometry.type === 'Points' ); + var hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + var buffergeometry = new THREE.BufferGeometry(); + + buffergeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) ); + + } else { + + buffergeometry.computeVertexNormals(); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.uvs.length > 0 ) { + + buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + var createdMaterials = []; + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + var material = undefined; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) { + + var materialLine = new THREE.LineBasicMaterial(); + materialLine.copy( material ); + materialLine.lights = false; // TOFIX + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) { + + var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } ); + materialLine.copy( material ); + material = materialPoints; + + } + + } + + if ( ! material ) { + + if ( isLine ) { + + material = new THREE.LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new THREE.MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + + } + + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors; + + createdMaterials.push( material ); + + } + + // Create mesh + + var mesh; + + if ( createdMaterials.length > 1 ) { + + for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + var sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + console.timeEnd( 'OBJLoader' ); + + return container; + + } + + }; + + return OBJLoader; + +} )(); diff --git a/static/exercises/visual_odometry_3D/3DScene/OrbitControls.js b/static/exercises/visual_odometry_3D/3DScene/OrbitControls.js new file mode 100644 index 000000000..63e0a6f63 --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/OrbitControls.js @@ -0,0 +1,1055 @@ +/** + * @author qiao / https://github.com/qiao + * @author mrdoob / http://mrdoob.com + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author erich666 / http://erichaines.com + */ + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one finger move +// Zoom - middle mouse, or mousewheel / touch: two finger spread or squish +// Pan - right mouse, or arrow keys / touch: three finger swipe + +THREE.OrbitControls = function ( object, domElement ) { + + this.object = object; + + this.domElement = ( domElement !== undefined ) ? domElement : document; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new THREE.Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.25; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + this.keyzoomSpeed = 1.1; + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 , ZOOM_INCREASE1: 171, + ZOOM_INCREASE2: 107, ZOOM_REDUCE1: 173, ZOOM_REDUCE2: 109}; + + // Mouse buttons + this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new THREE.Vector3(); + + // so camera.up is the orbit axis + var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().inverse(); + + var lastPosition = new THREE.Vector3(); + var lastQuaternion = new THREE.Quaternion(); + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + // restrict theta to be between desired limits + spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + scope.target.add( panOffset ); + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + } + + scale = 1; + panOffset.set( 0, 0, 0 ); + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + window.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { NONE: - 1, ROTATE: 0, DOLLY: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_DOLLY: 4, TOUCH_PAN: 5 }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new THREE.Spherical(); + var sphericalDelta = new THREE.Spherical(); + + var scale = 1; + var panOffset = new THREE.Vector3(); + var zoomChanged = false; + + var rotateStart = new THREE.Vector2(); + var rotateEnd = new THREE.Vector2(); + var rotateDelta = new THREE.Vector2(); + + var panStart = new THREE.Vector2(); + var panEnd = new THREE.Vector2(); + var panDelta = new THREE.Vector2(); + + var dollyStart = new THREE.Vector2(); + var dollyEnd = new THREE.Vector2(); + var dollyDelta = new THREE.Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new THREE.Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new THREE.Vector3(); + + return function panUp( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); // get Y column of objectMatrix + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new THREE.Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we actually don't use screenWidth, since perspective camera is fixed to screen height + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + //console.log( 'handleMouseDownRotate' ); + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + //console.log( 'handleMouseDownDolly' ); + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + //console.log( 'handleMouseDownPan' ); + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + //console.log( 'handleMouseMoveRotate' ); + + rotateEnd.set( event.clientX, event.clientY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + // rotating across whole screen goes 360 degrees around + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + //console.log( 'handleMouseMoveDolly' ); + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyOut( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + //console.log( 'handleMouseMovePan' ); + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( event ) { + + // console.log( 'handleMouseUp' ); + + } + + function handleMouseWheel( event ) { + + // console.log( 'handleMouseWheel' ); + + if ( event.deltaY < 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyIn( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + //console.log( 'handleKeyDown' ); + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + scope.update(); + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + scope.update(); + break; + + case scope.keys.ZOOM_INCREASE1: + case scope.keys.ZOOM_INCREASE2: + dollyIn(scope.keyzoomSpeed); + scope.update(); + break; + + case scope.keys.ZOOM_REDUCE1: + case scope.keys.ZOOM_REDUCE2: + dollyOut(scope.keyzoomSpeed); + scope.update(); + break; + + } + + } + + function handleTouchStartRotate( event ) { + + //console.log( 'handleTouchStartRotate' ); + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } + + function handleTouchStartDolly( event ) { + + //console.log( 'handleTouchStartDolly' ); + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartPan( event ) { + + //console.log( 'handleTouchStartPan' ); + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } + + function handleTouchMoveRotate( event ) { + + //console.log( 'handleTouchMoveRotate' ); + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + rotateDelta.subVectors( rotateEnd, rotateStart ); + + var element = scope.domElement === document ? scope.domElement.body : scope.domElement; + + // rotating across whole screen goes 360 degrees around + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed ); + + // rotating up and down along whole screen attempts to go 360, but limited to 180 + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleTouchMoveDolly( event ) { + + //console.log( 'handleTouchMoveDolly' ); + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleTouchMovePan( event ) { + + //console.log( 'handleTouchMovePan' ); + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + panDelta.subVectors( panEnd, panStart ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleTouchEnd( event ) { + + //console.log( 'handleTouchEnd' ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.button ) { + + case scope.mouseButtons.ORBIT: + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + break; + + case scope.mouseButtons.ZOOM: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case scope.mouseButtons.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + break; + + } + + if ( state !== STATE.NONE ) { + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + handleMouseWheel( event ); + + scope.dispatchEvent( startEvent ); // not sure why these are here... + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case 2: // two-fingered touch: dolly + + if ( scope.enableZoom === false ) return; + + handleTouchStartDolly( event ); + + state = STATE.TOUCH_DOLLY; + + break; + + case 3: // three-fingered touch: pan + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( event.touches.length ) { + + case 1: // one-fingered touch: rotate + + if ( scope.enableRotate === false ) return; + if ( state !== STATE.TOUCH_ROTATE ) return; // is this needed?... + + handleTouchMoveRotate( event ); + + break; + + case 2: // two-fingered touch: dolly + + if ( scope.enableZoom === false ) return; + if ( state !== STATE.TOUCH_DOLLY ) return; // is this needed?... + + handleTouchMoveDolly( event ); + + break; + + case 3: // three-fingered touch: pan + + if ( scope.enablePan === false ) return; + if ( state !== STATE.TOUCH_PAN ) return; // is this needed?... + + handleTouchMovePan( event ); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); + + window.addEventListener( 'keydown', onKeyDown, false ); + + // force an update at start + + this.update(); + +}; + +THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype ); +THREE.OrbitControls.prototype.constructor = THREE.OrbitControls; + +Object.defineProperties( THREE.OrbitControls.prototype, { + + center: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .center has been renamed to .target' ); + return this.target; + + } + + }, + + // backward compatibility + + noZoom: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + return ! this.enableZoom; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.' ); + this.enableZoom = ! value; + + } + + }, + + noRotate: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + return ! this.enableRotate; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.' ); + this.enableRotate = ! value; + + } + + }, + + noPan: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + return ! this.enablePan; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.' ); + this.enablePan = ! value; + + } + + }, + + noKeys: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + return ! this.enableKeys; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.' ); + this.enableKeys = ! value; + + } + + }, + + staticMoving: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + return ! this.enableDamping; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.' ); + this.enableDamping = ! value; + + } + + }, + + dynamicDampingFactor: { + + get: function () { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + return this.dampingFactor; + + }, + + set: function ( value ) { + + console.warn( 'THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.' ); + this.dampingFactor = value; + + } + + } + +} ); diff --git a/static/exercises/visual_odometry_3D/3DScene/pose3d.js b/static/exercises/visual_odometry_3D/3DScene/pose3d.js new file mode 100644 index 000000000..1b2b01f3d --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/pose3d.js @@ -0,0 +1,33 @@ +function getYaw(qw,qx,qy,qz) { + var rotateZa0=2.0*(qx*qy + qw*qz); + var rotateZa1=qw*qw + qx*qx - qy*qy - qz*qz; + var rotateZ=0.0; + if(rotateZa0 != 0.0 && rotateZa1 != 0.0){ + rotateZ=Math.atan2(rotateZa0,rotateZa1); + } + return rotateZ; +} + +function getRoll(qw,qx,qy,qz){ + rotateXa0=2.0*(qy*qz + qw*qx); + rotateXa1=qw*qw - qx*qx - qy*qy + qz*qz; + rotateX=0.0; + + if(rotateXa0 != 0.0 && rotateXa1 !=0.0){ + rotateX=Math.atan2(rotateXa0, rotateXa1); + } + return rotateX; +} +function getPitch(qw,qx,qy,qz){ + rotateYa0=-2.0*(qx*qz - qw*qy); + rotateY=0.0; + if(rotateYa0>=1.0){ + rotateY=Math.PI/2.0; + } else if(rotateYa0<=-1.0){ + rotateY=-Math.PI/2.0 + } else { + rotateY=Math.asin(rotateYa0) + } + + return rotateY; +} diff --git a/static/exercises/visual_odometry_3D/3DScene/three.js b/static/exercises/visual_odometry_3D/3DScene/three.js new file mode 100644 index 000000000..eccb44bd8 --- /dev/null +++ b/static/exercises/visual_odometry_3D/3DScene/three.js @@ -0,0 +1,45930 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.THREE = {}))); +}(this, (function (exports) { 'use strict'; + + // Polyfills + + if ( Number.EPSILON === undefined ) { + + Number.EPSILON = Math.pow( 2, - 52 ); + + } + + if ( Number.isInteger === undefined ) { + + // Missing in IE + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isInteger + + Number.isInteger = function ( value ) { + + return typeof value === 'number' && isFinite( value ) && Math.floor( value ) === value; + + }; + + } + + // + + if ( Math.sign === undefined ) { + + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/sign + + Math.sign = function ( x ) { + + return ( x < 0 ) ? - 1 : ( x > 0 ) ? 1 : + x; + + }; + + } + + if ( 'name' in Function.prototype === false ) { + + // Missing in IE + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name + + Object.defineProperty( Function.prototype, 'name', { + + get: function () { + + return this.toString().match( /^\s*function\s*([^\(\s]*)/ )[ 1 ]; + + } + + } ); + + } + + if ( Object.assign === undefined ) { + + // Missing in IE + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign + + ( function () { + + Object.assign = function ( target ) { + + if ( target === undefined || target === null ) { + + throw new TypeError( 'Cannot convert undefined or null to object' ); + + } + + var output = Object( target ); + + for ( var index = 1; index < arguments.length; index ++ ) { + + var source = arguments[ index ]; + + if ( source !== undefined && source !== null ) { + + for ( var nextKey in source ) { + + if ( Object.prototype.hasOwnProperty.call( source, nextKey ) ) { + + output[ nextKey ] = source[ nextKey ]; + + } + + } + + } + + } + + return output; + + }; + + } )(); + + } + + /** + * https://github.com/mrdoob/eventdispatcher.js/ + */ + + function EventDispatcher() {} + + Object.assign( EventDispatcher.prototype, { + + addEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) this._listeners = {}; + + var listeners = this._listeners; + + if ( listeners[ type ] === undefined ) { + + listeners[ type ] = []; + + } + + if ( listeners[ type ].indexOf( listener ) === - 1 ) { + + listeners[ type ].push( listener ); + + } + + }, + + hasEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return false; + + var listeners = this._listeners; + + return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1; + + }, + + removeEventListener: function ( type, listener ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ type ]; + + if ( listenerArray !== undefined ) { + + var index = listenerArray.indexOf( listener ); + + if ( index !== - 1 ) { + + listenerArray.splice( index, 1 ); + + } + + } + + }, + + dispatchEvent: function ( event ) { + + if ( this._listeners === undefined ) return; + + var listeners = this._listeners; + var listenerArray = listeners[ event.type ]; + + if ( listenerArray !== undefined ) { + + event.target = this; + + var array = listenerArray.slice( 0 ); + + for ( var i = 0, l = array.length; i < l; i ++ ) { + + array[ i ].call( this, event ); + + } + + } + + } + + } ); + + var REVISION = '91dev'; + var MOUSE = { LEFT: 0, MIDDLE: 1, RIGHT: 2 }; + var CullFaceNone = 0; + var CullFaceBack = 1; + var CullFaceFront = 2; + var CullFaceFrontBack = 3; + var FrontFaceDirectionCW = 0; + var FrontFaceDirectionCCW = 1; + var BasicShadowMap = 0; + var PCFShadowMap = 1; + var PCFSoftShadowMap = 2; + var FrontSide = 0; + var BackSide = 1; + var DoubleSide = 2; + var FlatShading = 1; + var SmoothShading = 2; + var NoColors = 0; + var FaceColors = 1; + var VertexColors = 2; + var NoBlending = 0; + var NormalBlending = 1; + var AdditiveBlending = 2; + var SubtractiveBlending = 3; + var MultiplyBlending = 4; + var CustomBlending = 5; + var AddEquation = 100; + var SubtractEquation = 101; + var ReverseSubtractEquation = 102; + var MinEquation = 103; + var MaxEquation = 104; + var ZeroFactor = 200; + var OneFactor = 201; + var SrcColorFactor = 202; + var OneMinusSrcColorFactor = 203; + var SrcAlphaFactor = 204; + var OneMinusSrcAlphaFactor = 205; + var DstAlphaFactor = 206; + var OneMinusDstAlphaFactor = 207; + var DstColorFactor = 208; + var OneMinusDstColorFactor = 209; + var SrcAlphaSaturateFactor = 210; + var NeverDepth = 0; + var AlwaysDepth = 1; + var LessDepth = 2; + var LessEqualDepth = 3; + var EqualDepth = 4; + var GreaterEqualDepth = 5; + var GreaterDepth = 6; + var NotEqualDepth = 7; + var MultiplyOperation = 0; + var MixOperation = 1; + var AddOperation = 2; + var NoToneMapping = 0; + var LinearToneMapping = 1; + var ReinhardToneMapping = 2; + var Uncharted2ToneMapping = 3; + var CineonToneMapping = 4; + var UVMapping = 300; + var CubeReflectionMapping = 301; + var CubeRefractionMapping = 302; + var EquirectangularReflectionMapping = 303; + var EquirectangularRefractionMapping = 304; + var SphericalReflectionMapping = 305; + var CubeUVReflectionMapping = 306; + var CubeUVRefractionMapping = 307; + var RepeatWrapping = 1000; + var ClampToEdgeWrapping = 1001; + var MirroredRepeatWrapping = 1002; + var NearestFilter = 1003; + var NearestMipMapNearestFilter = 1004; + var NearestMipMapLinearFilter = 1005; + var LinearFilter = 1006; + var LinearMipMapNearestFilter = 1007; + var LinearMipMapLinearFilter = 1008; + var UnsignedByteType = 1009; + var ByteType = 1010; + var ShortType = 1011; + var UnsignedShortType = 1012; + var IntType = 1013; + var UnsignedIntType = 1014; + var FloatType = 1015; + var HalfFloatType = 1016; + var UnsignedShort4444Type = 1017; + var UnsignedShort5551Type = 1018; + var UnsignedShort565Type = 1019; + var UnsignedInt248Type = 1020; + var AlphaFormat = 1021; + var RGBFormat = 1022; + var RGBAFormat = 1023; + var LuminanceFormat = 1024; + var LuminanceAlphaFormat = 1025; + var RGBEFormat = RGBAFormat; + var DepthFormat = 1026; + var DepthStencilFormat = 1027; + var RGB_S3TC_DXT1_Format = 33776; + var RGBA_S3TC_DXT1_Format = 33777; + var RGBA_S3TC_DXT3_Format = 33778; + var RGBA_S3TC_DXT5_Format = 33779; + var RGB_PVRTC_4BPPV1_Format = 35840; + var RGB_PVRTC_2BPPV1_Format = 35841; + var RGBA_PVRTC_4BPPV1_Format = 35842; + var RGBA_PVRTC_2BPPV1_Format = 35843; + var RGB_ETC1_Format = 36196; + var RGBA_ASTC_4x4_Format = 37808; + var RGBA_ASTC_5x4_Format = 37809; + var RGBA_ASTC_5x5_Format = 37810; + var RGBA_ASTC_6x5_Format = 37811; + var RGBA_ASTC_6x6_Format = 37812; + var RGBA_ASTC_8x5_Format = 37813; + var RGBA_ASTC_8x6_Format = 37814; + var RGBA_ASTC_8x8_Format = 37815; + var RGBA_ASTC_10x5_Format = 37816; + var RGBA_ASTC_10x6_Format = 37817; + var RGBA_ASTC_10x8_Format = 37818; + var RGBA_ASTC_10x10_Format = 37819; + var RGBA_ASTC_12x10_Format = 37820; + var RGBA_ASTC_12x12_Format = 37821; + var LoopOnce = 2200; + var LoopRepeat = 2201; + var LoopPingPong = 2202; + var InterpolateDiscrete = 2300; + var InterpolateLinear = 2301; + var InterpolateSmooth = 2302; + var ZeroCurvatureEnding = 2400; + var ZeroSlopeEnding = 2401; + var WrapAroundEnding = 2402; + var TrianglesDrawMode = 0; + var TriangleStripDrawMode = 1; + var TriangleFanDrawMode = 2; + var LinearEncoding = 3000; + var sRGBEncoding = 3001; + var GammaEncoding = 3007; + var RGBEEncoding = 3002; + var LogLuvEncoding = 3003; + var RGBM7Encoding = 3004; + var RGBM16Encoding = 3005; + var RGBDEncoding = 3006; + var BasicDepthPacking = 3200; + var RGBADepthPacking = 3201; + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + + var _Math = { + + DEG2RAD: Math.PI / 180, + RAD2DEG: 180 / Math.PI, + + generateUUID: ( function () { + + // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 + + var lut = []; + + for ( var i = 0; i < 256; i ++ ) { + + lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ).toUpperCase(); + + } + + return function generateUUID() { + + var d0 = Math.random() * 0xffffffff | 0; + var d1 = Math.random() * 0xffffffff | 0; + var d2 = Math.random() * 0xffffffff | 0; + var d3 = Math.random() * 0xffffffff | 0; + return lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' + + lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' + + lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] + + lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ]; + + }; + + } )(), + + clamp: function ( value, min, max ) { + + return Math.max( min, Math.min( max, value ) ); + + }, + + // compute euclidian modulo of m % n + // https://en.wikipedia.org/wiki/Modulo_operation + + euclideanModulo: function ( n, m ) { + + return ( ( n % m ) + m ) % m; + + }, + + // Linear mapping from range to range + + mapLinear: function ( x, a1, a2, b1, b2 ) { + + return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 ); + + }, + + // https://en.wikipedia.org/wiki/Linear_interpolation + + lerp: function ( x, y, t ) { + + return ( 1 - t ) * x + t * y; + + }, + + // http://en.wikipedia.org/wiki/Smoothstep + + smoothstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * ( 3 - 2 * x ); + + }, + + smootherstep: function ( x, min, max ) { + + if ( x <= min ) return 0; + if ( x >= max ) return 1; + + x = ( x - min ) / ( max - min ); + + return x * x * x * ( x * ( x * 6 - 15 ) + 10 ); + + }, + + // Random integer from interval + + randInt: function ( low, high ) { + + return low + Math.floor( Math.random() * ( high - low + 1 ) ); + + }, + + // Random float from interval + + randFloat: function ( low, high ) { + + return low + Math.random() * ( high - low ); + + }, + + // Random float from <-range/2, range/2> interval + + randFloatSpread: function ( range ) { + + return range * ( 0.5 - Math.random() ); + + }, + + degToRad: function ( degrees ) { + + return degrees * _Math.DEG2RAD; + + }, + + radToDeg: function ( radians ) { + + return radians * _Math.RAD2DEG; + + }, + + isPowerOfTwo: function ( value ) { + + return ( value & ( value - 1 ) ) === 0 && value !== 0; + + }, + + ceilPowerOfTwo: function ( value ) { + + return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) ); + + }, + + floorPowerOfTwo: function ( value ) { + + return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) ); + + } + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author philogb / http://blog.thejit.org/ + * @author egraether / http://egraether.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + + function Vector2( x, y ) { + + this.x = x || 0; + this.y = y || 0; + + } + + Object.defineProperties( Vector2.prototype, { + + "width": { + + get: function () { + + return this.x; + + }, + + set: function ( value ) { + + this.x = value; + + } + + }, + + "height": { + + get: function () { + + return this.y; + + }, + + set: function ( value ) { + + this.y = value; + + } + + } + + } ); + + Object.assign( Vector2.prototype, { + + isVector2: true, + + set: function ( x, y ) { + + this.x = x; + this.y = y; + + return this; + + }, + + setScalar: function ( scalar ) { + + this.x = scalar; + this.y = scalar; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + clone: function () { + + return new this.constructor( this.x, this.y ); + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + + return this; + + }, + + addScaledVector: function ( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + + return this; + + }, + + subScalar: function ( s ) { + + this.x -= s; + this.y -= s; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + + return this; + + }, + + multiply: function ( v ) { + + this.x *= v.x; + this.y *= v.y; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + + return this; + + }, + + divide: function ( v ) { + + this.x /= v.x; + this.y /= v.y; + + return this; + + }, + + divideScalar: function ( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + }, + + applyMatrix3: function ( m ) { + + var x = this.x, y = this.y; + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ]; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ]; + + return this; + + }, + + min: function ( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + + return this; + + }, + + max: function ( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + + return this; + + }, + + clamp: function ( min, max ) { + + // assumes min < max, componentwise + + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + + return this; + + }, + + clampScalar: function () { + + var min = new Vector2(); + var max = new Vector2(); + + return function clampScalar( minVal, maxVal ) { + + min.set( minVal, minVal ); + max.set( maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + }(), + + clampLength: function ( min, max ) { + + var length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + + }, + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y ); + + }, + + manhattanLength: function () { + + return Math.abs( this.x ) + Math.abs( this.y ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() || 1 ); + + }, + + angle: function () { + + // computes the angle in radians with respect to the positive x-axis + + var angle = Math.atan2( this.y, this.x ); + + if ( angle < 0 ) angle += 2 * Math.PI; + + return angle; + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x, dy = this.y - v.y; + return dx * dx + dy * dy; + + }, + + manhattanDistanceTo: function ( v ) { + + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ); + + }, + + setLength: function ( length ) { + + return this.normalize().multiplyScalar( length ); + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + + return this; + + }, + + lerpVectors: function ( v1, v2, alpha ) { + + return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) ); + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + + return array; + + }, + + fromBufferAttribute: function ( attribute, index, offset ) { + + if ( offset !== undefined ) { + + console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' ); + + } + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + + return this; + + }, + + rotateAround: function ( center, angle ) { + + var c = Math.cos( angle ), s = Math.sin( angle ); + + var x = this.x - center.x; + var y = this.y - center.y; + + this.x = x * c - y * s + center.x; + this.y = x * s + y * c + center.y; + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author jordi_ros / http://plattsoft.com + * @author D1plo1d / http://github.com/D1plo1d + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author timknip / http://www.floorplanner.com/ + * @author bhouston / http://clara.io + * @author WestLangley / http://github.com/WestLangley + */ + + function Matrix4() { + + this.elements = [ + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ]; + + if ( arguments.length > 0 ) { + + console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' ); + + } + + } + + Object.assign( Matrix4.prototype, { + + isMatrix4: true, + + set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) { + + var te = this.elements; + + te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14; + te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24; + te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34; + te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + clone: function () { + + return new Matrix4().fromArray( this.elements ); + + }, + + copy: function ( m ) { + + var te = this.elements; + var me = m.elements; + + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ]; + te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; + te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ]; + te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ]; + + return this; + + }, + + copyPosition: function ( m ) { + + var te = this.elements, me = m.elements; + + te[ 12 ] = me[ 12 ]; + te[ 13 ] = me[ 13 ]; + te[ 14 ] = me[ 14 ]; + + return this; + + }, + + extractBasis: function ( xAxis, yAxis, zAxis ) { + + xAxis.setFromMatrixColumn( this, 0 ); + yAxis.setFromMatrixColumn( this, 1 ); + zAxis.setFromMatrixColumn( this, 2 ); + + return this; + + }, + + makeBasis: function ( xAxis, yAxis, zAxis ) { + + this.set( + xAxis.x, yAxis.x, zAxis.x, 0, + xAxis.y, yAxis.y, zAxis.y, 0, + xAxis.z, yAxis.z, zAxis.z, 0, + 0, 0, 0, 1 + ); + + return this; + + }, + + extractRotation: function () { + + var v1 = new Vector3(); + + return function extractRotation( m ) { + + var te = this.elements; + var me = m.elements; + + var scaleX = 1 / v1.setFromMatrixColumn( m, 0 ).length(); + var scaleY = 1 / v1.setFromMatrixColumn( m, 1 ).length(); + var scaleZ = 1 / v1.setFromMatrixColumn( m, 2 ).length(); + + te[ 0 ] = me[ 0 ] * scaleX; + te[ 1 ] = me[ 1 ] * scaleX; + te[ 2 ] = me[ 2 ] * scaleX; + + te[ 4 ] = me[ 4 ] * scaleY; + te[ 5 ] = me[ 5 ] * scaleY; + te[ 6 ] = me[ 6 ] * scaleY; + + te[ 8 ] = me[ 8 ] * scaleZ; + te[ 9 ] = me[ 9 ] * scaleZ; + te[ 10 ] = me[ 10 ] * scaleZ; + + return this; + + }; + + }(), + + makeRotationFromEuler: function ( euler ) { + + if ( ! ( euler && euler.isEuler ) ) { + + console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' ); + + } + + var te = this.elements; + + var x = euler.x, y = euler.y, z = euler.z; + var a = Math.cos( x ), b = Math.sin( x ); + var c = Math.cos( y ), d = Math.sin( y ); + var e = Math.cos( z ), f = Math.sin( z ); + + if ( euler.order === 'XYZ' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = - c * f; + te[ 8 ] = d; + + te[ 1 ] = af + be * d; + te[ 5 ] = ae - bf * d; + te[ 9 ] = - b * c; + + te[ 2 ] = bf - ae * d; + te[ 6 ] = be + af * d; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YXZ' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce + df * b; + te[ 4 ] = de * b - cf; + te[ 8 ] = a * d; + + te[ 1 ] = a * f; + te[ 5 ] = a * e; + te[ 9 ] = - b; + + te[ 2 ] = cf * b - de; + te[ 6 ] = df + ce * b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZXY' ) { + + var ce = c * e, cf = c * f, de = d * e, df = d * f; + + te[ 0 ] = ce - df * b; + te[ 4 ] = - a * f; + te[ 8 ] = de + cf * b; + + te[ 1 ] = cf + de * b; + te[ 5 ] = a * e; + te[ 9 ] = df - ce * b; + + te[ 2 ] = - a * d; + te[ 6 ] = b; + te[ 10 ] = a * c; + + } else if ( euler.order === 'ZYX' ) { + + var ae = a * e, af = a * f, be = b * e, bf = b * f; + + te[ 0 ] = c * e; + te[ 4 ] = be * d - af; + te[ 8 ] = ae * d + bf; + + te[ 1 ] = c * f; + te[ 5 ] = bf * d + ae; + te[ 9 ] = af * d - be; + + te[ 2 ] = - d; + te[ 6 ] = b * c; + te[ 10 ] = a * c; + + } else if ( euler.order === 'YZX' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = bd - ac * f; + te[ 8 ] = bc * f + ad; + + te[ 1 ] = f; + te[ 5 ] = a * e; + te[ 9 ] = - b * e; + + te[ 2 ] = - d * e; + te[ 6 ] = ad * f + bc; + te[ 10 ] = ac - bd * f; + + } else if ( euler.order === 'XZY' ) { + + var ac = a * c, ad = a * d, bc = b * c, bd = b * d; + + te[ 0 ] = c * e; + te[ 4 ] = - f; + te[ 8 ] = d * e; + + te[ 1 ] = ac * f + bd; + te[ 5 ] = a * e; + te[ 9 ] = ad * f - bc; + + te[ 2 ] = bc * f - ad; + te[ 6 ] = b * e; + te[ 10 ] = bd * f + ac; + + } + + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; + + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + }, + + makeRotationFromQuaternion: function ( q ) { + + var te = this.elements; + + var x = q._x, y = q._y, z = q._z, w = q._w; + var x2 = x + x, y2 = y + y, z2 = z + z; + var xx = x * x2, xy = x * y2, xz = x * z2; + var yy = y * y2, yz = y * z2, zz = z * z2; + var wx = w * x2, wy = w * y2, wz = w * z2; + + te[ 0 ] = 1 - ( yy + zz ); + te[ 4 ] = xy - wz; + te[ 8 ] = xz + wy; + + te[ 1 ] = xy + wz; + te[ 5 ] = 1 - ( xx + zz ); + te[ 9 ] = yz - wx; + + te[ 2 ] = xz - wy; + te[ 6 ] = yz + wx; + te[ 10 ] = 1 - ( xx + yy ); + + // last column + te[ 3 ] = 0; + te[ 7 ] = 0; + te[ 11 ] = 0; + + // bottom row + te[ 12 ] = 0; + te[ 13 ] = 0; + te[ 14 ] = 0; + te[ 15 ] = 1; + + return this; + + }, + + lookAt: function () { + + var x = new Vector3(); + var y = new Vector3(); + var z = new Vector3(); + + return function lookAt( eye, target, up ) { + + var te = this.elements; + + z.subVectors( eye, target ); + + if ( z.lengthSq() === 0 ) { + + // eye and target are in the same position + + z.z = 1; + + } + + z.normalize(); + x.crossVectors( up, z ); + + if ( x.lengthSq() === 0 ) { + + // up and z are parallel + + if ( Math.abs( up.z ) === 1 ) { + + z.x += 0.0001; + + } else { + + z.z += 0.0001; + + } + + z.normalize(); + x.crossVectors( up, z ); + + } + + x.normalize(); + y.crossVectors( z, x ); + + te[ 0 ] = x.x; te[ 4 ] = y.x; te[ 8 ] = z.x; + te[ 1 ] = x.y; te[ 5 ] = y.y; te[ 9 ] = z.y; + te[ 2 ] = x.z; te[ 6 ] = y.z; te[ 10 ] = z.z; + + return this; + + }; + + }(), + + multiply: function ( m, n ) { + + if ( n !== undefined ) { + + console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' ); + return this.multiplyMatrices( m, n ); + + } + + return this.multiplyMatrices( this, m ); + + }, + + premultiply: function ( m ) { + + return this.multiplyMatrices( m, this ); + + }, + + multiplyMatrices: function ( a, b ) { + + var ae = a.elements; + var be = b.elements; + var te = this.elements; + + var a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ]; + var a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ]; + var a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ]; + var a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ]; + + var b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ]; + var b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ]; + var b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ]; + var b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ]; + + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41; + te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42; + te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43; + te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44; + + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41; + te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42; + te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43; + te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44; + + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41; + te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42; + te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43; + te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44; + + te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41; + te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42; + te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43; + te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44; + + return this; + + }, + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s; + te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s; + te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s; + te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s; + + return this; + + }, + + applyToBufferAttribute: function () { + + var v1 = new Vector3(); + + return function applyToBufferAttribute( attribute ) { + + for ( var i = 0, l = attribute.count; i < l; i ++ ) { + + v1.x = attribute.getX( i ); + v1.y = attribute.getY( i ); + v1.z = attribute.getZ( i ); + + v1.applyMatrix4( this ); + + attribute.setXYZ( i, v1.x, v1.y, v1.z ); + + } + + return attribute; + + }; + + }(), + + determinant: function () { + + var te = this.elements; + + var n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ]; + var n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ]; + var n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ]; + var n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ]; + + //TODO: make this more efficient + //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm ) + + return ( + n41 * ( + + n14 * n23 * n32 + - n13 * n24 * n32 + - n14 * n22 * n33 + + n12 * n24 * n33 + + n13 * n22 * n34 + - n12 * n23 * n34 + ) + + n42 * ( + + n11 * n23 * n34 + - n11 * n24 * n33 + + n14 * n21 * n33 + - n13 * n21 * n34 + + n13 * n24 * n31 + - n14 * n23 * n31 + ) + + n43 * ( + + n11 * n24 * n32 + - n11 * n22 * n34 + - n14 * n21 * n32 + + n12 * n21 * n34 + + n14 * n22 * n31 + - n12 * n24 * n31 + ) + + n44 * ( + - n13 * n22 * n31 + - n11 * n23 * n32 + + n11 * n22 * n33 + + n13 * n21 * n32 + - n12 * n21 * n33 + + n12 * n23 * n31 + ) + + ); + + }, + + transpose: function () { + + var te = this.elements; + var tmp; + + tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp; + tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp; + tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp; + + tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp; + tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp; + tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp; + + return this; + + }, + + setPosition: function ( v ) { + + var te = this.elements; + + te[ 12 ] = v.x; + te[ 13 ] = v.y; + te[ 14 ] = v.z; + + return this; + + }, + + getInverse: function ( m, throwOnDegenerate ) { + + // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm + var te = this.elements, + me = m.elements, + + n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], n41 = me[ 3 ], + n12 = me[ 4 ], n22 = me[ 5 ], n32 = me[ 6 ], n42 = me[ 7 ], + n13 = me[ 8 ], n23 = me[ 9 ], n33 = me[ 10 ], n43 = me[ 11 ], + n14 = me[ 12 ], n24 = me[ 13 ], n34 = me[ 14 ], n44 = me[ 15 ], + + t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44, + t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44, + t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44, + t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34; + + var det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14; + + if ( det === 0 ) { + + var msg = "THREE.Matrix4: .getInverse() can't invert matrix, determinant is 0"; + + if ( throwOnDegenerate === true ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + return this.identity(); + + } + + var detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv; + te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv; + te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv; + + te[ 4 ] = t12 * detInv; + te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv; + te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv; + te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv; + + te[ 8 ] = t13 * detInv; + te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv; + te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv; + te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv; + + te[ 12 ] = t14 * detInv; + te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv; + te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv; + te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv; + + return this; + + }, + + scale: function ( v ) { + + var te = this.elements; + var x = v.x, y = v.y, z = v.z; + + te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z; + te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z; + te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z; + te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z; + + return this; + + }, + + getMaxScaleOnAxis: function () { + + var te = this.elements; + + var scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ]; + var scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ]; + var scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ]; + + return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) ); + + }, + + makeTranslation: function ( x, y, z ) { + + this.set( + + 1, 0, 0, x, + 0, 1, 0, y, + 0, 0, 1, z, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationX: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + 1, 0, 0, 0, + 0, c, - s, 0, + 0, s, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationY: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, 0, s, 0, + 0, 1, 0, 0, + - s, 0, c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationZ: function ( theta ) { + + var c = Math.cos( theta ), s = Math.sin( theta ); + + this.set( + + c, - s, 0, 0, + s, c, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeRotationAxis: function ( axis, angle ) { + + // Based on http://www.gamedev.net/reference/articles/article1199.asp + + var c = Math.cos( angle ); + var s = Math.sin( angle ); + var t = 1 - c; + var x = axis.x, y = axis.y, z = axis.z; + var tx = t * x, ty = t * y; + + this.set( + + tx * x + c, tx * y - s * z, tx * z + s * y, 0, + tx * y + s * z, ty * y + c, ty * z - s * x, 0, + tx * z - s * y, ty * z + s * x, t * z * z + c, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeScale: function ( x, y, z ) { + + this.set( + + x, 0, 0, 0, + 0, y, 0, 0, + 0, 0, z, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + makeShear: function ( x, y, z ) { + + this.set( + + 1, y, z, 0, + x, 1, z, 0, + x, y, 1, 0, + 0, 0, 0, 1 + + ); + + return this; + + }, + + compose: function ( position, quaternion, scale ) { + + this.makeRotationFromQuaternion( quaternion ); + this.scale( scale ); + this.setPosition( position ); + + return this; + + }, + + decompose: function () { + + var vector = new Vector3(); + var matrix = new Matrix4(); + + return function decompose( position, quaternion, scale ) { + + var te = this.elements; + + var sx = vector.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length(); + var sy = vector.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length(); + var sz = vector.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length(); + + // if determine is negative, we need to invert one scale + var det = this.determinant(); + if ( det < 0 ) sx = - sx; + + position.x = te[ 12 ]; + position.y = te[ 13 ]; + position.z = te[ 14 ]; + + // scale the rotation part + matrix.copy( this ); + + var invSX = 1 / sx; + var invSY = 1 / sy; + var invSZ = 1 / sz; + + matrix.elements[ 0 ] *= invSX; + matrix.elements[ 1 ] *= invSX; + matrix.elements[ 2 ] *= invSX; + + matrix.elements[ 4 ] *= invSY; + matrix.elements[ 5 ] *= invSY; + matrix.elements[ 6 ] *= invSY; + + matrix.elements[ 8 ] *= invSZ; + matrix.elements[ 9 ] *= invSZ; + matrix.elements[ 10 ] *= invSZ; + + quaternion.setFromRotationMatrix( matrix ); + + scale.x = sx; + scale.y = sy; + scale.z = sz; + + return this; + + }; + + }(), + + makePerspective: function ( left, right, top, bottom, near, far ) { + + if ( far === undefined ) { + + console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' ); + + } + + var te = this.elements; + var x = 2 * near / ( right - left ); + var y = 2 * near / ( top - bottom ); + + var a = ( right + left ) / ( right - left ); + var b = ( top + bottom ) / ( top - bottom ); + var c = - ( far + near ) / ( far - near ); + var d = - 2 * far * near / ( far - near ); + + te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0; + te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0; + + return this; + + }, + + makeOrthographic: function ( left, right, top, bottom, near, far ) { + + var te = this.elements; + var w = 1.0 / ( right - left ); + var h = 1.0 / ( top - bottom ); + var p = 1.0 / ( far - near ); + + var x = ( right + left ) * w; + var y = ( top + bottom ) * h; + var z = ( far + near ) * p; + + te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x; + te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y; + te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z; + te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1; + + return this; + + }, + + equals: function ( matrix ) { + + var te = this.elements; + var me = matrix.elements; + + for ( var i = 0; i < 16; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; + + } + + return true; + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + for ( var i = 0; i < 16; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + var te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + array[ offset + 3 ] = te[ 3 ]; + + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + + array[ offset + 8 ] = te[ 8 ]; + array[ offset + 9 ] = te[ 9 ]; + array[ offset + 10 ] = te[ 10 ]; + array[ offset + 11 ] = te[ 11 ]; + + array[ offset + 12 ] = te[ 12 ]; + array[ offset + 13 ] = te[ 13 ]; + array[ offset + 14 ] = te[ 14 ]; + array[ offset + 15 ] = te[ 15 ]; + + return array; + + } + + } ); + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://clara.io + */ + + function Quaternion( x, y, z, w ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._w = ( w !== undefined ) ? w : 1; + + } + + Object.assign( Quaternion, { + + slerp: function ( qa, qb, qm, t ) { + + return qm.copy( qa ).slerp( qb, t ); + + }, + + slerpFlat: function ( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) { + + // fuzz-free, array-based Quaternion SLERP operation + + var x0 = src0[ srcOffset0 + 0 ], + y0 = src0[ srcOffset0 + 1 ], + z0 = src0[ srcOffset0 + 2 ], + w0 = src0[ srcOffset0 + 3 ], + + x1 = src1[ srcOffset1 + 0 ], + y1 = src1[ srcOffset1 + 1 ], + z1 = src1[ srcOffset1 + 2 ], + w1 = src1[ srcOffset1 + 3 ]; + + if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) { + + var s = 1 - t, + + cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1, + + dir = ( cos >= 0 ? 1 : - 1 ), + sqrSin = 1 - cos * cos; + + // Skip the Slerp for tiny steps to avoid numeric problems: + if ( sqrSin > Number.EPSILON ) { + + var sin = Math.sqrt( sqrSin ), + len = Math.atan2( sin, cos * dir ); + + s = Math.sin( s * len ) / sin; + t = Math.sin( t * len ) / sin; + + } + + var tDir = t * dir; + + x0 = x0 * s + x1 * tDir; + y0 = y0 * s + y1 * tDir; + z0 = z0 * s + z1 * tDir; + w0 = w0 * s + w1 * tDir; + + // Normalize in case we just did a lerp: + if ( s === 1 - t ) { + + var f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 ); + + x0 *= f; + y0 *= f; + z0 *= f; + w0 *= f; + + } + + } + + dst[ dstOffset ] = x0; + dst[ dstOffset + 1 ] = y0; + dst[ dstOffset + 2 ] = z0; + dst[ dstOffset + 3 ] = w0; + + } + + } ); + + Object.defineProperties( Quaternion.prototype, { + + x: { + + get: function () { + + return this._x; + + }, + + set: function ( value ) { + + this._x = value; + this.onChangeCallback(); + + } + + }, + + y: { + + get: function () { + + return this._y; + + }, + + set: function ( value ) { + + this._y = value; + this.onChangeCallback(); + + } + + }, + + z: { + + get: function () { + + return this._z; + + }, + + set: function ( value ) { + + this._z = value; + this.onChangeCallback(); + + } + + }, + + w: { + + get: function () { + + return this._w; + + }, + + set: function ( value ) { + + this._w = value; + this.onChangeCallback(); + + } + + } + + } ); + + Object.assign( Quaternion.prototype, { + + set: function ( x, y, z, w ) { + + this._x = x; + this._y = y; + this._z = z; + this._w = w; + + this.onChangeCallback(); + + return this; + + }, + + clone: function () { + + return new this.constructor( this._x, this._y, this._z, this._w ); + + }, + + copy: function ( quaternion ) { + + this._x = quaternion.x; + this._y = quaternion.y; + this._z = quaternion.z; + this._w = quaternion.w; + + this.onChangeCallback(); + + return this; + + }, + + setFromEuler: function ( euler, update ) { + + if ( ! ( euler && euler.isEuler ) ) { + + throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' ); + + } + + var x = euler._x, y = euler._y, z = euler._z, order = euler.order; + + // http://www.mathworks.com/matlabcentral/fileexchange/ + // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/ + // content/SpinCalc.m + + var cos = Math.cos; + var sin = Math.sin; + + var c1 = cos( x / 2 ); + var c2 = cos( y / 2 ); + var c3 = cos( z / 2 ); + + var s1 = sin( x / 2 ); + var s2 = sin( y / 2 ); + var s3 = sin( z / 2 ); + + if ( order === 'XYZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( order === 'YXZ' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( order === 'ZXY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( order === 'ZYX' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } else if ( order === 'YZX' ) { + + this._x = s1 * c2 * c3 + c1 * s2 * s3; + this._y = c1 * s2 * c3 + s1 * c2 * s3; + this._z = c1 * c2 * s3 - s1 * s2 * c3; + this._w = c1 * c2 * c3 - s1 * s2 * s3; + + } else if ( order === 'XZY' ) { + + this._x = s1 * c2 * c3 - c1 * s2 * s3; + this._y = c1 * s2 * c3 - s1 * c2 * s3; + this._z = c1 * c2 * s3 + s1 * s2 * c3; + this._w = c1 * c2 * c3 + s1 * s2 * s3; + + } + + if ( update !== false ) this.onChangeCallback(); + + return this; + + }, + + setFromAxisAngle: function ( axis, angle ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm + + // assumes axis is normalized + + var halfAngle = angle / 2, s = Math.sin( halfAngle ); + + this._x = axis.x * s; + this._y = axis.y * s; + this._z = axis.z * s; + this._w = Math.cos( halfAngle ); + + this.onChangeCallback(); + + return this; + + }, + + setFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ], + + trace = m11 + m22 + m33, + s; + + if ( trace > 0 ) { + + s = 0.5 / Math.sqrt( trace + 1.0 ); + + this._w = 0.25 / s; + this._x = ( m32 - m23 ) * s; + this._y = ( m13 - m31 ) * s; + this._z = ( m21 - m12 ) * s; + + } else if ( m11 > m22 && m11 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 ); + + this._w = ( m32 - m23 ) / s; + this._x = 0.25 * s; + this._y = ( m12 + m21 ) / s; + this._z = ( m13 + m31 ) / s; + + } else if ( m22 > m33 ) { + + s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 ); + + this._w = ( m13 - m31 ) / s; + this._x = ( m12 + m21 ) / s; + this._y = 0.25 * s; + this._z = ( m23 + m32 ) / s; + + } else { + + s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 ); + + this._w = ( m21 - m12 ) / s; + this._x = ( m13 + m31 ) / s; + this._y = ( m23 + m32 ) / s; + this._z = 0.25 * s; + + } + + this.onChangeCallback(); + + return this; + + }, + + setFromUnitVectors: function () { + + // assumes direction vectors vFrom and vTo are normalized + + var v1 = new Vector3(); + var r; + + var EPS = 0.000001; + + return function setFromUnitVectors( vFrom, vTo ) { + + if ( v1 === undefined ) v1 = new Vector3(); + + r = vFrom.dot( vTo ) + 1; + + if ( r < EPS ) { + + r = 0; + + if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) { + + v1.set( - vFrom.y, vFrom.x, 0 ); + + } else { + + v1.set( 0, - vFrom.z, vFrom.y ); + + } + + } else { + + v1.crossVectors( vFrom, vTo ); + + } + + this._x = v1.x; + this._y = v1.y; + this._z = v1.z; + this._w = r; + + return this.normalize(); + + }; + + }(), + + inverse: function () { + + // quaternion is assumed to have unit length + + return this.conjugate(); + + }, + + conjugate: function () { + + this._x *= - 1; + this._y *= - 1; + this._z *= - 1; + + this.onChangeCallback(); + + return this; + + }, + + dot: function ( v ) { + + return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w; + + }, + + lengthSq: function () { + + return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w; + + }, + + length: function () { + + return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w ); + + }, + + normalize: function () { + + var l = this.length(); + + if ( l === 0 ) { + + this._x = 0; + this._y = 0; + this._z = 0; + this._w = 1; + + } else { + + l = 1 / l; + + this._x = this._x * l; + this._y = this._y * l; + this._z = this._z * l; + this._w = this._w * l; + + } + + this.onChangeCallback(); + + return this; + + }, + + multiply: function ( q, p ) { + + if ( p !== undefined ) { + + console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' ); + return this.multiplyQuaternions( q, p ); + + } + + return this.multiplyQuaternions( this, q ); + + }, + + premultiply: function ( q ) { + + return this.multiplyQuaternions( q, this ); + + }, + + multiplyQuaternions: function ( a, b ) { + + // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm + + var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w; + var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w; + + this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby; + this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz; + this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx; + this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz; + + this.onChangeCallback(); + + return this; + + }, + + slerp: function ( qb, t ) { + + if ( t === 0 ) return this; + if ( t === 1 ) return this.copy( qb ); + + var x = this._x, y = this._y, z = this._z, w = this._w; + + // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/ + + var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z; + + if ( cosHalfTheta < 0 ) { + + this._w = - qb._w; + this._x = - qb._x; + this._y = - qb._y; + this._z = - qb._z; + + cosHalfTheta = - cosHalfTheta; + + } else { + + this.copy( qb ); + + } + + if ( cosHalfTheta >= 1.0 ) { + + this._w = w; + this._x = x; + this._y = y; + this._z = z; + + return this; + + } + + var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta ); + + if ( Math.abs( sinHalfTheta ) < 0.001 ) { + + this._w = 0.5 * ( w + this._w ); + this._x = 0.5 * ( x + this._x ); + this._y = 0.5 * ( y + this._y ); + this._z = 0.5 * ( z + this._z ); + + return this; + + } + + var halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta ); + var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta, + ratioB = Math.sin( t * halfTheta ) / sinHalfTheta; + + this._w = ( w * ratioA + this._w * ratioB ); + this._x = ( x * ratioA + this._x * ratioB ); + this._y = ( y * ratioA + this._y * ratioB ); + this._z = ( z * ratioA + this._z * ratioB ); + + this.onChangeCallback(); + + return this; + + }, + + equals: function ( quaternion ) { + + return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w ); + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + this._x = array[ offset ]; + this._y = array[ offset + 1 ]; + this._z = array[ offset + 2 ]; + this._w = array[ offset + 3 ]; + + this.onChangeCallback(); + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._w; + + return array; + + }, + + onChange: function ( callback ) { + + this.onChangeCallback = callback; + + return this; + + }, + + onChangeCallback: function () {} + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author kile / http://kile.stravaganza.org/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function Vector3( x, y, z ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + + } + + Object.assign( Vector3.prototype, { + + isVector3: true, + + set: function ( x, y, z ) { + + this.x = x; + this.y = y; + this.z = z; + + return this; + + }, + + setScalar: function ( scalar ) { + + this.x = scalar; + this.y = scalar; + this.z = scalar; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + clone: function () { + + return new this.constructor( this.x, this.y, this.z ); + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + + return this; + + }, + + addScaledVector: function ( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + + return this; + + }, + + subScalar: function ( s ) { + + this.x -= s; + this.y -= s; + this.z -= s; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + + return this; + + }, + + multiply: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' ); + return this.multiplyVectors( v, w ); + + } + + this.x *= v.x; + this.y *= v.y; + this.z *= v.z; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + + return this; + + }, + + multiplyVectors: function ( a, b ) { + + this.x = a.x * b.x; + this.y = a.y * b.y; + this.z = a.z * b.z; + + return this; + + }, + + applyEuler: function () { + + var quaternion = new Quaternion(); + + return function applyEuler( euler ) { + + if ( ! ( euler && euler.isEuler ) ) { + + console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' ); + + } + + return this.applyQuaternion( quaternion.setFromEuler( euler ) ); + + }; + + }(), + + applyAxisAngle: function () { + + var quaternion = new Quaternion(); + + return function applyAxisAngle( axis, angle ) { + + return this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) ); + + }; + + }(), + + applyMatrix3: function ( m ) { + + var x = this.x, y = this.y, z = this.z; + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z; + this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z; + this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z; + + return this; + + }, + + applyMatrix4: function ( m ) { + + var x = this.x, y = this.y, z = this.z; + var e = m.elements; + + var w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] ); + + this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w; + this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w; + this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w; + + return this; + + }, + + applyQuaternion: function ( q ) { + + var x = this.x, y = this.y, z = this.z; + var qx = q.x, qy = q.y, qz = q.z, qw = q.w; + + // calculate quat * vector + + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = - qx * x - qy * y - qz * z; + + // calculate result * inverse quat + + this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy; + this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz; + this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx; + + return this; + + }, + + project: function () { + + var matrix = new Matrix4(); + + return function project( camera ) { + + matrix.multiplyMatrices( camera.projectionMatrix, matrix.getInverse( camera.matrixWorld ) ); + return this.applyMatrix4( matrix ); + + }; + + }(), + + unproject: function () { + + var matrix = new Matrix4(); + + return function unproject( camera ) { + + matrix.multiplyMatrices( camera.matrixWorld, matrix.getInverse( camera.projectionMatrix ) ); + return this.applyMatrix4( matrix ); + + }; + + }(), + + transformDirection: function ( m ) { + + // input: THREE.Matrix4 affine matrix + // vector interpreted as a direction + + var x = this.x, y = this.y, z = this.z; + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z; + + return this.normalize(); + + }, + + divide: function ( v ) { + + this.x /= v.x; + this.y /= v.y; + this.z /= v.z; + + return this; + + }, + + divideScalar: function ( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + }, + + min: function ( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); + + return this; + + }, + + max: function ( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + + return this; + + }, + + clamp: function ( min, max ) { + + // assumes min < max, componentwise + + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + + return this; + + }, + + clampScalar: function () { + + var min = new Vector3(); + var max = new Vector3(); + + return function clampScalar( minVal, maxVal ) { + + min.set( minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + }(), + + clampLength: function ( min, max ) { + + var length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + + }, + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z; + + }, + + // TODO lengthSquared? + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); + + }, + + manhattanLength: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() || 1 ); + + }, + + setLength: function ( length ) { + + return this.normalize().multiplyScalar( length ); + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + + return this; + + }, + + lerpVectors: function ( v1, v2, alpha ) { + + return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); + + }, + + cross: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' ); + return this.crossVectors( v, w ); + + } + + return this.crossVectors( this, v ); + + }, + + crossVectors: function ( a, b ) { + + var ax = a.x, ay = a.y, az = a.z; + var bx = b.x, by = b.y, bz = b.z; + + this.x = ay * bz - az * by; + this.y = az * bx - ax * bz; + this.z = ax * by - ay * bx; + + return this; + + }, + + projectOnVector: function ( vector ) { + + var scalar = vector.dot( this ) / vector.lengthSq(); + + return this.copy( vector ).multiplyScalar( scalar ); + + }, + + projectOnPlane: function () { + + var v1 = new Vector3(); + + return function projectOnPlane( planeNormal ) { + + v1.copy( this ).projectOnVector( planeNormal ); + + return this.sub( v1 ); + + }; + + }(), + + reflect: function () { + + // reflect incident vector off plane orthogonal to normal + // normal is assumed to have unit length + + var v1 = new Vector3(); + + return function reflect( normal ) { + + return this.sub( v1.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) ); + + }; + + }(), + + angleTo: function ( v ) { + + var theta = this.dot( v ) / ( Math.sqrt( this.lengthSq() * v.lengthSq() ) ); + + // clamp, to handle numerical problems + + return Math.acos( _Math.clamp( theta, - 1, 1 ) ); + + }, + + distanceTo: function ( v ) { + + return Math.sqrt( this.distanceToSquared( v ) ); + + }, + + distanceToSquared: function ( v ) { + + var dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z; + + return dx * dx + dy * dy + dz * dz; + + }, + + manhattanDistanceTo: function ( v ) { + + return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z ); + + }, + + setFromSpherical: function ( s ) { + + var sinPhiRadius = Math.sin( s.phi ) * s.radius; + + this.x = sinPhiRadius * Math.sin( s.theta ); + this.y = Math.cos( s.phi ) * s.radius; + this.z = sinPhiRadius * Math.cos( s.theta ); + + return this; + + }, + + setFromCylindrical: function ( c ) { + + this.x = c.radius * Math.sin( c.theta ); + this.y = c.y; + this.z = c.radius * Math.cos( c.theta ); + + return this; + + }, + + setFromMatrixPosition: function ( m ) { + + var e = m.elements; + + this.x = e[ 12 ]; + this.y = e[ 13 ]; + this.z = e[ 14 ]; + + return this; + + }, + + setFromMatrixScale: function ( m ) { + + var sx = this.setFromMatrixColumn( m, 0 ).length(); + var sy = this.setFromMatrixColumn( m, 1 ).length(); + var sz = this.setFromMatrixColumn( m, 2 ).length(); + + this.x = sx; + this.y = sy; + this.z = sz; + + return this; + + }, + + setFromMatrixColumn: function ( m, index ) { + + return this.fromArray( m.elements, index * 4 ); + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) ); + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + + return array; + + }, + + fromBufferAttribute: function ( attribute, index, offset ) { + + if ( offset !== undefined ) { + + console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' ); + + } + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + + return this; + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://clara.io + * @author tschw + */ + + function Matrix3() { + + this.elements = [ + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ]; + + if ( arguments.length > 0 ) { + + console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' ); + + } + + } + + Object.assign( Matrix3.prototype, { + + isMatrix3: true, + + set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) { + + var te = this.elements; + + te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31; + te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32; + te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33; + + return this; + + }, + + identity: function () { + + this.set( + + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + + ); + + return this; + + }, + + clone: function () { + + return new this.constructor().fromArray( this.elements ); + + }, + + copy: function ( m ) { + + var te = this.elements; + var me = m.elements; + + te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; + te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; + te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ]; + + return this; + + }, + + setFromMatrix4: function ( m ) { + + var me = m.elements; + + this.set( + + me[ 0 ], me[ 4 ], me[ 8 ], + me[ 1 ], me[ 5 ], me[ 9 ], + me[ 2 ], me[ 6 ], me[ 10 ] + + ); + + return this; + + }, + + applyToBufferAttribute: function () { + + var v1 = new Vector3(); + + return function applyToBufferAttribute( attribute ) { + + for ( var i = 0, l = attribute.count; i < l; i ++ ) { + + v1.x = attribute.getX( i ); + v1.y = attribute.getY( i ); + v1.z = attribute.getZ( i ); + + v1.applyMatrix3( this ); + + attribute.setXYZ( i, v1.x, v1.y, v1.z ); + + } + + return attribute; + + }; + + }(), + + multiply: function ( m ) { + + return this.multiplyMatrices( this, m ); + + }, + + premultiply: function ( m ) { + + return this.multiplyMatrices( m, this ); + + }, + + multiplyMatrices: function ( a, b ) { + + var ae = a.elements; + var be = b.elements; + var te = this.elements; + + var a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ]; + var a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ]; + var a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ]; + + var b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ]; + var b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ]; + var b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ]; + + te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31; + te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32; + te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33; + + te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31; + te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32; + te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33; + + te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31; + te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32; + te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33; + + return this; + + }, + + multiplyScalar: function ( s ) { + + var te = this.elements; + + te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s; + te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s; + te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s; + + return this; + + }, + + determinant: function () { + + var te = this.elements; + + var a = te[ 0 ], b = te[ 1 ], c = te[ 2 ], + d = te[ 3 ], e = te[ 4 ], f = te[ 5 ], + g = te[ 6 ], h = te[ 7 ], i = te[ 8 ]; + + return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g; + + }, + + getInverse: function ( matrix, throwOnDegenerate ) { + + if ( matrix && matrix.isMatrix4 ) { + + console.error( "THREE.Matrix3: .getInverse() no longer takes a Matrix4 argument." ); + + } + + var me = matrix.elements, + te = this.elements, + + n11 = me[ 0 ], n21 = me[ 1 ], n31 = me[ 2 ], + n12 = me[ 3 ], n22 = me[ 4 ], n32 = me[ 5 ], + n13 = me[ 6 ], n23 = me[ 7 ], n33 = me[ 8 ], + + t11 = n33 * n22 - n32 * n23, + t12 = n32 * n13 - n33 * n12, + t13 = n23 * n12 - n22 * n13, + + det = n11 * t11 + n21 * t12 + n31 * t13; + + if ( det === 0 ) { + + var msg = "THREE.Matrix3: .getInverse() can't invert matrix, determinant is 0"; + + if ( throwOnDegenerate === true ) { + + throw new Error( msg ); + + } else { + + console.warn( msg ); + + } + + return this.identity(); + + } + + var detInv = 1 / det; + + te[ 0 ] = t11 * detInv; + te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv; + te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv; + + te[ 3 ] = t12 * detInv; + te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv; + te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv; + + te[ 6 ] = t13 * detInv; + te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv; + te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv; + + return this; + + }, + + transpose: function () { + + var tmp, m = this.elements; + + tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp; + tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp; + tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp; + + return this; + + }, + + getNormalMatrix: function ( matrix4 ) { + + return this.setFromMatrix4( matrix4 ).getInverse( this ).transpose(); + + }, + + transposeIntoArray: function ( r ) { + + var m = this.elements; + + r[ 0 ] = m[ 0 ]; + r[ 1 ] = m[ 3 ]; + r[ 2 ] = m[ 6 ]; + r[ 3 ] = m[ 1 ]; + r[ 4 ] = m[ 4 ]; + r[ 5 ] = m[ 7 ]; + r[ 6 ] = m[ 2 ]; + r[ 7 ] = m[ 5 ]; + r[ 8 ] = m[ 8 ]; + + return this; + + }, + + setUvTransform: function ( tx, ty, sx, sy, rotation, cx, cy ) { + + var c = Math.cos( rotation ); + var s = Math.sin( rotation ); + + this.set( + sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx, + - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty, + 0, 0, 1 + ); + + }, + + scale: function ( sx, sy ) { + + var te = this.elements; + + te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx; + te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy; + + return this; + + }, + + rotate: function ( theta ) { + + var c = Math.cos( theta ); + var s = Math.sin( theta ); + + var te = this.elements; + + var a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ]; + var a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ]; + + te[ 0 ] = c * a11 + s * a21; + te[ 3 ] = c * a12 + s * a22; + te[ 6 ] = c * a13 + s * a23; + + te[ 1 ] = - s * a11 + c * a21; + te[ 4 ] = - s * a12 + c * a22; + te[ 7 ] = - s * a13 + c * a23; + + return this; + + }, + + translate: function ( tx, ty ) { + + var te = this.elements; + + te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ]; + te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ]; + + return this; + + }, + + equals: function ( matrix ) { + + var te = this.elements; + var me = matrix.elements; + + for ( var i = 0; i < 9; i ++ ) { + + if ( te[ i ] !== me[ i ] ) return false; + + } + + return true; + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + for ( var i = 0; i < 9; i ++ ) { + + this.elements[ i ] = array[ i + offset ]; + + } + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + var te = this.elements; + + array[ offset ] = te[ 0 ]; + array[ offset + 1 ] = te[ 1 ]; + array[ offset + 2 ] = te[ 2 ]; + + array[ offset + 3 ] = te[ 3 ]; + array[ offset + 4 ] = te[ 4 ]; + array[ offset + 5 ] = te[ 5 ]; + + array[ offset + 6 ] = te[ 6 ]; + array[ offset + 7 ] = te[ 7 ]; + array[ offset + 8 ] = te[ 8 ]; + + return array; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + */ + + var textureId = 0; + + function Texture( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) { + + Object.defineProperty( this, 'id', { value: textureId ++ } ); + + this.uuid = _Math.generateUUID(); + + this.name = ''; + + this.image = image !== undefined ? image : Texture.DEFAULT_IMAGE; + this.mipmaps = []; + + this.mapping = mapping !== undefined ? mapping : Texture.DEFAULT_MAPPING; + + this.wrapS = wrapS !== undefined ? wrapS : ClampToEdgeWrapping; + this.wrapT = wrapT !== undefined ? wrapT : ClampToEdgeWrapping; + + this.magFilter = magFilter !== undefined ? magFilter : LinearFilter; + this.minFilter = minFilter !== undefined ? minFilter : LinearMipMapLinearFilter; + + this.anisotropy = anisotropy !== undefined ? anisotropy : 1; + + this.format = format !== undefined ? format : RGBAFormat; + this.type = type !== undefined ? type : UnsignedByteType; + + this.offset = new Vector2( 0, 0 ); + this.repeat = new Vector2( 1, 1 ); + this.center = new Vector2( 0, 0 ); + this.rotation = 0; + + this.matrixAutoUpdate = true; + this.matrix = new Matrix3(); + + this.generateMipmaps = true; + this.premultiplyAlpha = false; + this.flipY = true; + this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml) + + // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap. + // + // Also changing the encoding after already used by a Material will not automatically make the Material + // update. You need to explicitly call Material.needsUpdate to trigger it to recompile. + this.encoding = encoding !== undefined ? encoding : LinearEncoding; + + this.version = 0; + this.onUpdate = null; + + } + + Texture.DEFAULT_IMAGE = undefined; + Texture.DEFAULT_MAPPING = UVMapping; + + Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Texture, + + isTexture: true, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.name = source.name; + + this.image = source.image; + this.mipmaps = source.mipmaps.slice( 0 ); + + this.mapping = source.mapping; + + this.wrapS = source.wrapS; + this.wrapT = source.wrapT; + + this.magFilter = source.magFilter; + this.minFilter = source.minFilter; + + this.anisotropy = source.anisotropy; + + this.format = source.format; + this.type = source.type; + + this.offset.copy( source.offset ); + this.repeat.copy( source.repeat ); + this.center.copy( source.center ); + this.rotation = source.rotation; + + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrix.copy( source.matrix ); + + this.generateMipmaps = source.generateMipmaps; + this.premultiplyAlpha = source.premultiplyAlpha; + this.flipY = source.flipY; + this.unpackAlignment = source.unpackAlignment; + this.encoding = source.encoding; + + return this; + + }, + + toJSON: function ( meta ) { + + var isRootObject = ( meta === undefined || typeof meta === 'string' ); + + if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) { + + return meta.textures[ this.uuid ]; + + } + + function getDataURL( image ) { + + var canvas; + + if ( image instanceof HTMLCanvasElement ) { + + canvas = image; + + } else { + + canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + var context = canvas.getContext( '2d' ); + + if ( image instanceof ImageData ) { + + context.putImageData( image, 0, 0 ); + + } else { + + context.drawImage( image, 0, 0, image.width, image.height ); + + } + + } + + if ( canvas.width > 2048 || canvas.height > 2048 ) { + + return canvas.toDataURL( 'image/jpeg', 0.6 ); + + } else { + + return canvas.toDataURL( 'image/png' ); + + } + + } + + var output = { + metadata: { + version: 4.5, + type: 'Texture', + generator: 'Texture.toJSON' + }, + + uuid: this.uuid, + name: this.name, + + mapping: this.mapping, + + repeat: [ this.repeat.x, this.repeat.y ], + offset: [ this.offset.x, this.offset.y ], + center: [ this.center.x, this.center.y ], + rotation: this.rotation, + + wrap: [ this.wrapS, this.wrapT ], + + minFilter: this.minFilter, + magFilter: this.magFilter, + anisotropy: this.anisotropy, + + flipY: this.flipY + }; + + if ( this.image !== undefined ) { + + // TODO: Move to THREE.Image + + var image = this.image; + + if ( image.uuid === undefined ) { + + image.uuid = _Math.generateUUID(); // UGH + + } + + if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) { + + meta.images[ image.uuid ] = { + uuid: image.uuid, + url: getDataURL( image ) + }; + + } + + output.image = image.uuid; + + } + + if ( ! isRootObject ) { + + meta.textures[ this.uuid ] = output; + + } + + return output; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + }, + + transformUv: function ( uv ) { + + if ( this.mapping !== UVMapping ) return; + + uv.applyMatrix3( this.matrix ); + + if ( uv.x < 0 || uv.x > 1 ) { + + switch ( this.wrapS ) { + + case RepeatWrapping: + + uv.x = uv.x - Math.floor( uv.x ); + break; + + case ClampToEdgeWrapping: + + uv.x = uv.x < 0 ? 0 : 1; + break; + + case MirroredRepeatWrapping: + + if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) { + + uv.x = Math.ceil( uv.x ) - uv.x; + + } else { + + uv.x = uv.x - Math.floor( uv.x ); + + } + break; + + } + + } + + if ( uv.y < 0 || uv.y > 1 ) { + + switch ( this.wrapT ) { + + case RepeatWrapping: + + uv.y = uv.y - Math.floor( uv.y ); + break; + + case ClampToEdgeWrapping: + + uv.y = uv.y < 0 ? 0 : 1; + break; + + case MirroredRepeatWrapping: + + if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) { + + uv.y = Math.ceil( uv.y ) - uv.y; + + } else { + + uv.y = uv.y - Math.floor( uv.y ); + + } + break; + + } + + } + + if ( this.flipY ) { + + uv.y = 1 - uv.y; + + } + + } + + } ); + + Object.defineProperty( Texture.prototype, "needsUpdate", { + + set: function ( value ) { + + if ( value === true ) this.version ++; + + } + + } ); + + /** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author philogb / http://blog.thejit.org/ + * @author mikael emtinger / http://gomo.se/ + * @author egraether / http://egraether.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function Vector4( x, y, z, w ) { + + this.x = x || 0; + this.y = y || 0; + this.z = z || 0; + this.w = ( w !== undefined ) ? w : 1; + + } + + Object.assign( Vector4.prototype, { + + isVector4: true, + + set: function ( x, y, z, w ) { + + this.x = x; + this.y = y; + this.z = z; + this.w = w; + + return this; + + }, + + setScalar: function ( scalar ) { + + this.x = scalar; + this.y = scalar; + this.z = scalar; + this.w = scalar; + + return this; + + }, + + setX: function ( x ) { + + this.x = x; + + return this; + + }, + + setY: function ( y ) { + + this.y = y; + + return this; + + }, + + setZ: function ( z ) { + + this.z = z; + + return this; + + }, + + setW: function ( w ) { + + this.w = w; + + return this; + + }, + + setComponent: function ( index, value ) { + + switch ( index ) { + + case 0: this.x = value; break; + case 1: this.y = value; break; + case 2: this.z = value; break; + case 3: this.w = value; break; + default: throw new Error( 'index is out of range: ' + index ); + + } + + return this; + + }, + + getComponent: function ( index ) { + + switch ( index ) { + + case 0: return this.x; + case 1: return this.y; + case 2: return this.z; + case 3: return this.w; + default: throw new Error( 'index is out of range: ' + index ); + + } + + }, + + clone: function () { + + return new this.constructor( this.x, this.y, this.z, this.w ); + + }, + + copy: function ( v ) { + + this.x = v.x; + this.y = v.y; + this.z = v.z; + this.w = ( v.w !== undefined ) ? v.w : 1; + + return this; + + }, + + add: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' ); + return this.addVectors( v, w ); + + } + + this.x += v.x; + this.y += v.y; + this.z += v.z; + this.w += v.w; + + return this; + + }, + + addScalar: function ( s ) { + + this.x += s; + this.y += s; + this.z += s; + this.w += s; + + return this; + + }, + + addVectors: function ( a, b ) { + + this.x = a.x + b.x; + this.y = a.y + b.y; + this.z = a.z + b.z; + this.w = a.w + b.w; + + return this; + + }, + + addScaledVector: function ( v, s ) { + + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + this.w += v.w * s; + + return this; + + }, + + sub: function ( v, w ) { + + if ( w !== undefined ) { + + console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' ); + return this.subVectors( v, w ); + + } + + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + this.w -= v.w; + + return this; + + }, + + subScalar: function ( s ) { + + this.x -= s; + this.y -= s; + this.z -= s; + this.w -= s; + + return this; + + }, + + subVectors: function ( a, b ) { + + this.x = a.x - b.x; + this.y = a.y - b.y; + this.z = a.z - b.z; + this.w = a.w - b.w; + + return this; + + }, + + multiplyScalar: function ( scalar ) { + + this.x *= scalar; + this.y *= scalar; + this.z *= scalar; + this.w *= scalar; + + return this; + + }, + + applyMatrix4: function ( m ) { + + var x = this.x, y = this.y, z = this.z, w = this.w; + var e = m.elements; + + this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w; + this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w; + this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w; + this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w; + + return this; + + }, + + divideScalar: function ( scalar ) { + + return this.multiplyScalar( 1 / scalar ); + + }, + + setAxisAngleFromQuaternion: function ( q ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm + + // q is assumed to be normalized + + this.w = 2 * Math.acos( q.w ); + + var s = Math.sqrt( 1 - q.w * q.w ); + + if ( s < 0.0001 ) { + + this.x = 1; + this.y = 0; + this.z = 0; + + } else { + + this.x = q.x / s; + this.y = q.y / s; + this.z = q.z / s; + + } + + return this; + + }, + + setAxisAngleFromRotationMatrix: function ( m ) { + + // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var angle, x, y, z, // variables for result + epsilon = 0.01, // margin to allow for rounding errors + epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees + + te = m.elements, + + m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ], + m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ], + m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + if ( ( Math.abs( m12 - m21 ) < epsilon ) && + ( Math.abs( m13 - m31 ) < epsilon ) && + ( Math.abs( m23 - m32 ) < epsilon ) ) { + + // singularity found + // first check for identity matrix which must have +1 for all terms + // in leading diagonal and zero in other terms + + if ( ( Math.abs( m12 + m21 ) < epsilon2 ) && + ( Math.abs( m13 + m31 ) < epsilon2 ) && + ( Math.abs( m23 + m32 ) < epsilon2 ) && + ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) { + + // this singularity is identity matrix so angle = 0 + + this.set( 1, 0, 0, 0 ); + + return this; // zero angle, arbitrary axis + + } + + // otherwise this singularity is angle = 180 + + angle = Math.PI; + + var xx = ( m11 + 1 ) / 2; + var yy = ( m22 + 1 ) / 2; + var zz = ( m33 + 1 ) / 2; + var xy = ( m12 + m21 ) / 4; + var xz = ( m13 + m31 ) / 4; + var yz = ( m23 + m32 ) / 4; + + if ( ( xx > yy ) && ( xx > zz ) ) { + + // m11 is the largest diagonal term + + if ( xx < epsilon ) { + + x = 0; + y = 0.707106781; + z = 0.707106781; + + } else { + + x = Math.sqrt( xx ); + y = xy / x; + z = xz / x; + + } + + } else if ( yy > zz ) { + + // m22 is the largest diagonal term + + if ( yy < epsilon ) { + + x = 0.707106781; + y = 0; + z = 0.707106781; + + } else { + + y = Math.sqrt( yy ); + x = xy / y; + z = yz / y; + + } + + } else { + + // m33 is the largest diagonal term so base result on this + + if ( zz < epsilon ) { + + x = 0.707106781; + y = 0.707106781; + z = 0; + + } else { + + z = Math.sqrt( zz ); + x = xz / z; + y = yz / z; + + } + + } + + this.set( x, y, z, angle ); + + return this; // return 180 deg rotation + + } + + // as we have reached here there are no singularities so we can handle normally + + var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) + + ( m13 - m31 ) * ( m13 - m31 ) + + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize + + if ( Math.abs( s ) < 0.001 ) s = 1; + + // prevent divide by zero, should not happen if matrix is orthogonal and should be + // caught by singularity test above, but I've left it in just in case + + this.x = ( m32 - m23 ) / s; + this.y = ( m13 - m31 ) / s; + this.z = ( m21 - m12 ) / s; + this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 ); + + return this; + + }, + + min: function ( v ) { + + this.x = Math.min( this.x, v.x ); + this.y = Math.min( this.y, v.y ); + this.z = Math.min( this.z, v.z ); + this.w = Math.min( this.w, v.w ); + + return this; + + }, + + max: function ( v ) { + + this.x = Math.max( this.x, v.x ); + this.y = Math.max( this.y, v.y ); + this.z = Math.max( this.z, v.z ); + this.w = Math.max( this.w, v.w ); + + return this; + + }, + + clamp: function ( min, max ) { + + // assumes min < max, componentwise + + this.x = Math.max( min.x, Math.min( max.x, this.x ) ); + this.y = Math.max( min.y, Math.min( max.y, this.y ) ); + this.z = Math.max( min.z, Math.min( max.z, this.z ) ); + this.w = Math.max( min.w, Math.min( max.w, this.w ) ); + + return this; + + }, + + clampScalar: function () { + + var min, max; + + return function clampScalar( minVal, maxVal ) { + + if ( min === undefined ) { + + min = new Vector4(); + max = new Vector4(); + + } + + min.set( minVal, minVal, minVal, minVal ); + max.set( maxVal, maxVal, maxVal, maxVal ); + + return this.clamp( min, max ); + + }; + + }(), + + clampLength: function ( min, max ) { + + var length = this.length(); + + return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) ); + + }, + + floor: function () { + + this.x = Math.floor( this.x ); + this.y = Math.floor( this.y ); + this.z = Math.floor( this.z ); + this.w = Math.floor( this.w ); + + return this; + + }, + + ceil: function () { + + this.x = Math.ceil( this.x ); + this.y = Math.ceil( this.y ); + this.z = Math.ceil( this.z ); + this.w = Math.ceil( this.w ); + + return this; + + }, + + round: function () { + + this.x = Math.round( this.x ); + this.y = Math.round( this.y ); + this.z = Math.round( this.z ); + this.w = Math.round( this.w ); + + return this; + + }, + + roundToZero: function () { + + this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x ); + this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y ); + this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z ); + this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w ); + + return this; + + }, + + negate: function () { + + this.x = - this.x; + this.y = - this.y; + this.z = - this.z; + this.w = - this.w; + + return this; + + }, + + dot: function ( v ) { + + return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w; + + }, + + lengthSq: function () { + + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + + }, + + length: function () { + + return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w ); + + }, + + manhattanLength: function () { + + return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w ); + + }, + + normalize: function () { + + return this.divideScalar( this.length() || 1 ); + + }, + + setLength: function ( length ) { + + return this.normalize().multiplyScalar( length ); + + }, + + lerp: function ( v, alpha ) { + + this.x += ( v.x - this.x ) * alpha; + this.y += ( v.y - this.y ) * alpha; + this.z += ( v.z - this.z ) * alpha; + this.w += ( v.w - this.w ) * alpha; + + return this; + + }, + + lerpVectors: function ( v1, v2, alpha ) { + + return this.subVectors( v2, v1 ).multiplyScalar( alpha ).add( v1 ); + + }, + + equals: function ( v ) { + + return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) ); + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + this.x = array[ offset ]; + this.y = array[ offset + 1 ]; + this.z = array[ offset + 2 ]; + this.w = array[ offset + 3 ]; + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.x; + array[ offset + 1 ] = this.y; + array[ offset + 2 ] = this.z; + array[ offset + 3 ] = this.w; + + return array; + + }, + + fromBufferAttribute: function ( attribute, index, offset ) { + + if ( offset !== undefined ) { + + console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' ); + + } + + this.x = attribute.getX( index ); + this.y = attribute.getY( index ); + this.z = attribute.getZ( index ); + this.w = attribute.getW( index ); + + return this; + + } + + } ); + + /** + * @author szimek / https://github.com/szimek/ + * @author alteredq / http://alteredqualia.com/ + * @author Marius Kintel / https://github.com/kintel + */ + + /* + In options, we can specify: + * Texture parameters for an auto-generated target texture + * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers + */ + function WebGLRenderTarget( width, height, options ) { + + this.uuid = _Math.generateUUID(); + + this.width = width; + this.height = height; + + this.scissor = new Vector4( 0, 0, width, height ); + this.scissorTest = false; + + this.viewport = new Vector4( 0, 0, width, height ); + + options = options || {}; + + if ( options.minFilter === undefined ) options.minFilter = LinearFilter; + + this.texture = new Texture( undefined, undefined, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding ); + + this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true; + this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true; + this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null; + + } + + WebGLRenderTarget.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: WebGLRenderTarget, + + isWebGLRenderTarget: true, + + setSize: function ( width, height ) { + + if ( this.width !== width || this.height !== height ) { + + this.width = width; + this.height = height; + + this.dispose(); + + } + + this.viewport.set( 0, 0, width, height ); + this.scissor.set( 0, 0, width, height ); + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.width = source.width; + this.height = source.height; + + this.viewport.copy( source.viewport ); + + this.texture = source.texture.clone(); + + this.depthBuffer = source.depthBuffer; + this.stencilBuffer = source.stencilBuffer; + this.depthTexture = source.depthTexture; + + return this; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com + */ + + function WebGLRenderTargetCube( width, height, options ) { + + WebGLRenderTarget.call( this, width, height, options ); + + this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5 + this.activeMipMapLevel = 0; + + } + + WebGLRenderTargetCube.prototype = Object.create( WebGLRenderTarget.prototype ); + WebGLRenderTargetCube.prototype.constructor = WebGLRenderTargetCube; + + WebGLRenderTargetCube.prototype.isWebGLRenderTargetCube = true; + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) { + + Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); + + this.image = { data: data, width: width, height: height }; + + this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; + this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; + + this.generateMipmaps = false; + this.flipY = false; + this.unpackAlignment = 1; + + } + + DataTexture.prototype = Object.create( Texture.prototype ); + DataTexture.prototype.constructor = DataTexture; + + DataTexture.prototype.isDataTexture = true; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) { + + images = images !== undefined ? images : []; + mapping = mapping !== undefined ? mapping : CubeReflectionMapping; + + Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); + + this.flipY = false; + + } + + CubeTexture.prototype = Object.create( Texture.prototype ); + CubeTexture.prototype.constructor = CubeTexture; + + CubeTexture.prototype.isCubeTexture = true; + + Object.defineProperty( CubeTexture.prototype, 'images', { + + get: function () { + + return this.image; + + }, + + set: function ( value ) { + + this.image = value; + + } + + } ); + + /** + * @author tschw + * + * Uniforms of a program. + * Those form a tree structure with a special top-level container for the root, + * which you get by calling 'new WebGLUniforms( gl, program, renderer )'. + * + * + * Properties of inner nodes including the top-level container: + * + * .seq - array of nested uniforms + * .map - nested uniforms by name + * + * + * Methods of all nodes except the top-level container: + * + * .setValue( gl, value, [renderer] ) + * + * uploads a uniform value(s) + * the 'renderer' parameter is needed for sampler uniforms + * + * + * Static methods of the top-level container (renderer factorizations): + * + * .upload( gl, seq, values, renderer ) + * + * sets uniforms in 'seq' to 'values[id].value' + * + * .seqWithValue( seq, values ) : filteredSeq + * + * filters 'seq' entries with corresponding entry in values + * + * + * Methods of the top-level container (renderer factorizations): + * + * .setValue( gl, name, value ) + * + * sets uniform with name 'name' to 'value' + * + * .set( gl, obj, prop ) + * + * sets uniform from object and property with same name than uniform + * + * .setOptional( gl, obj, prop ) + * + * like .set for an optional property of the object + * + */ + + var emptyTexture = new Texture(); + var emptyCubeTexture = new CubeTexture(); + + // --- Base for inner nodes (including the root) --- + + function UniformContainer() { + + this.seq = []; + this.map = {}; + + } + + // --- Utilities --- + + // Array Caches (provide typed arrays for temporary by size) + + var arrayCacheF32 = []; + var arrayCacheI32 = []; + + // Float32Array caches used for uploading Matrix uniforms + + var mat4array = new Float32Array( 16 ); + var mat3array = new Float32Array( 9 ); + + // Flattening for arrays of vectors and matrices + + function flatten( array, nBlocks, blockSize ) { + + var firstElem = array[ 0 ]; + + if ( firstElem <= 0 || firstElem > 0 ) return array; + // unoptimized: ! isNaN( firstElem ) + // see http://jacksondunstan.com/articles/983 + + var n = nBlocks * blockSize, + r = arrayCacheF32[ n ]; + + if ( r === undefined ) { + + r = new Float32Array( n ); + arrayCacheF32[ n ] = r; + + } + + if ( nBlocks !== 0 ) { + + firstElem.toArray( r, 0 ); + + for ( var i = 1, offset = 0; i !== nBlocks; ++ i ) { + + offset += blockSize; + array[ i ].toArray( r, offset ); + + } + + } + + return r; + + } + + // Texture unit allocation + + function allocTexUnits( renderer, n ) { + + var r = arrayCacheI32[ n ]; + + if ( r === undefined ) { + + r = new Int32Array( n ); + arrayCacheI32[ n ] = r; + + } + + for ( var i = 0; i !== n; ++ i ) + r[ i ] = renderer.allocTextureUnit(); + + return r; + + } + + // --- Setters --- + + // Note: Defining these methods externally, because they come in a bunch + // and this way their names minify. + + // Single scalar + + function setValue1f( gl, v ) { + + gl.uniform1f( this.addr, v ); + + } + + function setValue1i( gl, v ) { + + gl.uniform1i( this.addr, v ); + + } + + // Single float vector (from flat array or THREE.VectorN) + + function setValue2fv( gl, v ) { + + if ( v.x === undefined ) { + + gl.uniform2fv( this.addr, v ); + + } else { + + gl.uniform2f( this.addr, v.x, v.y ); + + } + + } + + function setValue3fv( gl, v ) { + + if ( v.x !== undefined ) { + + gl.uniform3f( this.addr, v.x, v.y, v.z ); + + } else if ( v.r !== undefined ) { + + gl.uniform3f( this.addr, v.r, v.g, v.b ); + + } else { + + gl.uniform3fv( this.addr, v ); + + } + + } + + function setValue4fv( gl, v ) { + + if ( v.x === undefined ) { + + gl.uniform4fv( this.addr, v ); + + } else { + + gl.uniform4f( this.addr, v.x, v.y, v.z, v.w ); + + } + + } + + // Single matrix (from flat array or MatrixN) + + function setValue2fm( gl, v ) { + + gl.uniformMatrix2fv( this.addr, false, v.elements || v ); + + } + + function setValue3fm( gl, v ) { + + if ( v.elements === undefined ) { + + gl.uniformMatrix3fv( this.addr, false, v ); + + } else { + + mat3array.set( v.elements ); + gl.uniformMatrix3fv( this.addr, false, mat3array ); + + } + + } + + function setValue4fm( gl, v ) { + + if ( v.elements === undefined ) { + + gl.uniformMatrix4fv( this.addr, false, v ); + + } else { + + mat4array.set( v.elements ); + gl.uniformMatrix4fv( this.addr, false, mat4array ); + + } + + } + + // Single texture (2D / Cube) + + function setValueT1( gl, v, renderer ) { + + var unit = renderer.allocTextureUnit(); + gl.uniform1i( this.addr, unit ); + renderer.setTexture2D( v || emptyTexture, unit ); + + } + + function setValueT6( gl, v, renderer ) { + + var unit = renderer.allocTextureUnit(); + gl.uniform1i( this.addr, unit ); + renderer.setTextureCube( v || emptyCubeTexture, unit ); + + } + + // Integer / Boolean vectors or arrays thereof (always flat arrays) + + function setValue2iv( gl, v ) { + + gl.uniform2iv( this.addr, v ); + + } + + function setValue3iv( gl, v ) { + + gl.uniform3iv( this.addr, v ); + + } + + function setValue4iv( gl, v ) { + + gl.uniform4iv( this.addr, v ); + + } + + // Helper to pick the right setter for the singular case + + function getSingularSetter( type ) { + + switch ( type ) { + + case 0x1406: return setValue1f; // FLOAT + case 0x8b50: return setValue2fv; // _VEC2 + case 0x8b51: return setValue3fv; // _VEC3 + case 0x8b52: return setValue4fv; // _VEC4 + + case 0x8b5a: return setValue2fm; // _MAT2 + case 0x8b5b: return setValue3fm; // _MAT3 + case 0x8b5c: return setValue4fm; // _MAT4 + + case 0x8b5e: case 0x8d66: return setValueT1; // SAMPLER_2D, SAMPLER_EXTERNAL_OES + case 0x8b60: return setValueT6; // SAMPLER_CUBE + + case 0x1404: case 0x8b56: return setValue1i; // INT, BOOL + case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2 + case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3 + case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4 + + } + + } + + // Array of scalars + + function setValue1fv( gl, v ) { + + gl.uniform1fv( this.addr, v ); + + } + function setValue1iv( gl, v ) { + + gl.uniform1iv( this.addr, v ); + + } + + // Array of vectors (flat or from THREE classes) + + function setValueV2a( gl, v ) { + + gl.uniform2fv( this.addr, flatten( v, this.size, 2 ) ); + + } + + function setValueV3a( gl, v ) { + + gl.uniform3fv( this.addr, flatten( v, this.size, 3 ) ); + + } + + function setValueV4a( gl, v ) { + + gl.uniform4fv( this.addr, flatten( v, this.size, 4 ) ); + + } + + // Array of matrices (flat or from THREE clases) + + function setValueM2a( gl, v ) { + + gl.uniformMatrix2fv( this.addr, false, flatten( v, this.size, 4 ) ); + + } + + function setValueM3a( gl, v ) { + + gl.uniformMatrix3fv( this.addr, false, flatten( v, this.size, 9 ) ); + + } + + function setValueM4a( gl, v ) { + + gl.uniformMatrix4fv( this.addr, false, flatten( v, this.size, 16 ) ); + + } + + // Array of textures (2D / Cube) + + function setValueT1a( gl, v, renderer ) { + + var n = v.length, + units = allocTexUnits( renderer, n ); + + gl.uniform1iv( this.addr, units ); + + for ( var i = 0; i !== n; ++ i ) { + + renderer.setTexture2D( v[ i ] || emptyTexture, units[ i ] ); + + } + + } + + function setValueT6a( gl, v, renderer ) { + + var n = v.length, + units = allocTexUnits( renderer, n ); + + gl.uniform1iv( this.addr, units ); + + for ( var i = 0; i !== n; ++ i ) { + + renderer.setTextureCube( v[ i ] || emptyCubeTexture, units[ i ] ); + + } + + } + + // Helper to pick the right setter for a pure (bottom-level) array + + function getPureArraySetter( type ) { + + switch ( type ) { + + case 0x1406: return setValue1fv; // FLOAT + case 0x8b50: return setValueV2a; // _VEC2 + case 0x8b51: return setValueV3a; // _VEC3 + case 0x8b52: return setValueV4a; // _VEC4 + + case 0x8b5a: return setValueM2a; // _MAT2 + case 0x8b5b: return setValueM3a; // _MAT3 + case 0x8b5c: return setValueM4a; // _MAT4 + + case 0x8b5e: return setValueT1a; // SAMPLER_2D + case 0x8b60: return setValueT6a; // SAMPLER_CUBE + + case 0x1404: case 0x8b56: return setValue1iv; // INT, BOOL + case 0x8b53: case 0x8b57: return setValue2iv; // _VEC2 + case 0x8b54: case 0x8b58: return setValue3iv; // _VEC3 + case 0x8b55: case 0x8b59: return setValue4iv; // _VEC4 + + } + + } + + // --- Uniform Classes --- + + function SingleUniform( id, activeInfo, addr ) { + + this.id = id; + this.addr = addr; + this.setValue = getSingularSetter( activeInfo.type ); + + // this.path = activeInfo.name; // DEBUG + + } + + function PureArrayUniform( id, activeInfo, addr ) { + + this.id = id; + this.addr = addr; + this.size = activeInfo.size; + this.setValue = getPureArraySetter( activeInfo.type ); + + // this.path = activeInfo.name; // DEBUG + + } + + function StructuredUniform( id ) { + + this.id = id; + + UniformContainer.call( this ); // mix-in + + } + + StructuredUniform.prototype.setValue = function ( gl, value ) { + + // Note: Don't need an extra 'renderer' parameter, since samplers + // are not allowed in structured uniforms. + + var seq = this.seq; + + for ( var i = 0, n = seq.length; i !== n; ++ i ) { + + var u = seq[ i ]; + u.setValue( gl, value[ u.id ] ); + + } + + }; + + // --- Top-level --- + + // Parser - builds up the property tree from the path strings + + var RePathPart = /([\w\d_]+)(\])?(\[|\.)?/g; + + // extracts + // - the identifier (member name or array index) + // - followed by an optional right bracket (found when array index) + // - followed by an optional left bracket or dot (type of subscript) + // + // Note: These portions can be read in a non-overlapping fashion and + // allow straightforward parsing of the hierarchy that WebGL encodes + // in the uniform names. + + function addUniform( container, uniformObject ) { + + container.seq.push( uniformObject ); + container.map[ uniformObject.id ] = uniformObject; + + } + + function parseUniform( activeInfo, addr, container ) { + + var path = activeInfo.name, + pathLength = path.length; + + // reset RegExp object, because of the early exit of a previous run + RePathPart.lastIndex = 0; + + for ( ; ; ) { + + var match = RePathPart.exec( path ), + matchEnd = RePathPart.lastIndex, + + id = match[ 1 ], + idIsIndex = match[ 2 ] === ']', + subscript = match[ 3 ]; + + if ( idIsIndex ) id = id | 0; // convert to integer + + if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) { + + // bare name or "pure" bottom-level array "[0]" suffix + + addUniform( container, subscript === undefined ? + new SingleUniform( id, activeInfo, addr ) : + new PureArrayUniform( id, activeInfo, addr ) ); + + break; + + } else { + + // step into inner node / create it in case it doesn't exist + + var map = container.map, next = map[ id ]; + + if ( next === undefined ) { + + next = new StructuredUniform( id ); + addUniform( container, next ); + + } + + container = next; + + } + + } + + } + + // Root Container + + function WebGLUniforms( gl, program, renderer ) { + + UniformContainer.call( this ); + + this.renderer = renderer; + + var n = gl.getProgramParameter( program, gl.ACTIVE_UNIFORMS ); + + for ( var i = 0; i < n; ++ i ) { + + var info = gl.getActiveUniform( program, i ), + path = info.name, + addr = gl.getUniformLocation( program, path ); + + parseUniform( info, addr, this ); + + } + + } + + WebGLUniforms.prototype.setValue = function ( gl, name, value ) { + + var u = this.map[ name ]; + + if ( u !== undefined ) u.setValue( gl, value, this.renderer ); + + }; + + WebGLUniforms.prototype.setOptional = function ( gl, object, name ) { + + var v = object[ name ]; + + if ( v !== undefined ) this.setValue( gl, name, v ); + + }; + + + // Static interface + + WebGLUniforms.upload = function ( gl, seq, values, renderer ) { + + for ( var i = 0, n = seq.length; i !== n; ++ i ) { + + var u = seq[ i ], + v = values[ u.id ]; + + if ( v.needsUpdate !== false ) { + + // note: always updating when .needsUpdate is undefined + u.setValue( gl, v.value, renderer ); + + } + + } + + }; + + WebGLUniforms.seqWithValue = function ( seq, values ) { + + var r = []; + + for ( var i = 0, n = seq.length; i !== n; ++ i ) { + + var u = seq[ i ]; + if ( u.id in values ) r.push( u ); + + } + + return r; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + var ColorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF, + 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2, + 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50, + 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B, + 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B, + 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F, + 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3, + 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222, + 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700, + 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4, + 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00, + 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3, + 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA, + 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32, + 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3, + 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC, + 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD, + 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6, + 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9, + 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F, + 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE, + 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA, + 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0, + 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 }; + + function Color( r, g, b ) { + + if ( g === undefined && b === undefined ) { + + // r is THREE.Color, hex or string + return this.set( r ); + + } + + return this.setRGB( r, g, b ); + + } + + Object.assign( Color.prototype, { + + isColor: true, + + r: 1, g: 1, b: 1, + + set: function ( value ) { + + if ( value && value.isColor ) { + + this.copy( value ); + + } else if ( typeof value === 'number' ) { + + this.setHex( value ); + + } else if ( typeof value === 'string' ) { + + this.setStyle( value ); + + } + + return this; + + }, + + setScalar: function ( scalar ) { + + this.r = scalar; + this.g = scalar; + this.b = scalar; + + return this; + + }, + + setHex: function ( hex ) { + + hex = Math.floor( hex ); + + this.r = ( hex >> 16 & 255 ) / 255; + this.g = ( hex >> 8 & 255 ) / 255; + this.b = ( hex & 255 ) / 255; + + return this; + + }, + + setRGB: function ( r, g, b ) { + + this.r = r; + this.g = g; + this.b = b; + + return this; + + }, + + setHSL: function () { + + function hue2rgb( p, q, t ) { + + if ( t < 0 ) t += 1; + if ( t > 1 ) t -= 1; + if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t; + if ( t < 1 / 2 ) return q; + if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t ); + return p; + + } + + return function setHSL( h, s, l ) { + + // h,s,l ranges are in 0.0 - 1.0 + h = _Math.euclideanModulo( h, 1 ); + s = _Math.clamp( s, 0, 1 ); + l = _Math.clamp( l, 0, 1 ); + + if ( s === 0 ) { + + this.r = this.g = this.b = l; + + } else { + + var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s ); + var q = ( 2 * l ) - p; + + this.r = hue2rgb( q, p, h + 1 / 3 ); + this.g = hue2rgb( q, p, h ); + this.b = hue2rgb( q, p, h - 1 / 3 ); + + } + + return this; + + }; + + }(), + + setStyle: function ( style ) { + + function handleAlpha( string ) { + + if ( string === undefined ) return; + + if ( parseFloat( string ) < 1 ) { + + console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' ); + + } + + } + + + var m; + + if ( m = /^((?:rgb|hsl)a?)\(\s*([^\)]*)\)/.exec( style ) ) { + + // rgb / hsl + + var color; + var name = m[ 1 ]; + var components = m[ 2 ]; + + switch ( name ) { + + case 'rgb': + case 'rgba': + + if ( color = /^(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { + + // rgb(255,0,0) rgba(255,0,0,0.5) + this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255; + this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255; + this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255; + + handleAlpha( color[ 5 ] ); + + return this; + + } + + if ( color = /^(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { + + // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5) + this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100; + this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100; + this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100; + + handleAlpha( color[ 5 ] ); + + return this; + + } + + break; + + case 'hsl': + case 'hsla': + + if ( color = /^([0-9]*\.?[0-9]+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(,\s*([0-9]*\.?[0-9]+)\s*)?$/.exec( components ) ) { + + // hsl(120,50%,50%) hsla(120,50%,50%,0.5) + var h = parseFloat( color[ 1 ] ) / 360; + var s = parseInt( color[ 2 ], 10 ) / 100; + var l = parseInt( color[ 3 ], 10 ) / 100; + + handleAlpha( color[ 5 ] ); + + return this.setHSL( h, s, l ); + + } + + break; + + } + + } else if ( m = /^\#([A-Fa-f0-9]+)$/.exec( style ) ) { + + // hex color + + var hex = m[ 1 ]; + var size = hex.length; + + if ( size === 3 ) { + + // #ff0 + this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255; + this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255; + this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255; + + return this; + + } else if ( size === 6 ) { + + // #ff0000 + this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255; + this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255; + this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255; + + return this; + + } + + } + + if ( style && style.length > 0 ) { + + // color keywords + var hex = ColorKeywords[ style ]; + + if ( hex !== undefined ) { + + // red + this.setHex( hex ); + + } else { + + // unknown color + console.warn( 'THREE.Color: Unknown color ' + style ); + + } + + } + + return this; + + }, + + clone: function () { + + return new this.constructor( this.r, this.g, this.b ); + + }, + + copy: function ( color ) { + + this.r = color.r; + this.g = color.g; + this.b = color.b; + + return this; + + }, + + copyGammaToLinear: function ( color, gammaFactor ) { + + if ( gammaFactor === undefined ) gammaFactor = 2.0; + + this.r = Math.pow( color.r, gammaFactor ); + this.g = Math.pow( color.g, gammaFactor ); + this.b = Math.pow( color.b, gammaFactor ); + + return this; + + }, + + copyLinearToGamma: function ( color, gammaFactor ) { + + if ( gammaFactor === undefined ) gammaFactor = 2.0; + + var safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0; + + this.r = Math.pow( color.r, safeInverse ); + this.g = Math.pow( color.g, safeInverse ); + this.b = Math.pow( color.b, safeInverse ); + + return this; + + }, + + convertGammaToLinear: function () { + + var r = this.r, g = this.g, b = this.b; + + this.r = r * r; + this.g = g * g; + this.b = b * b; + + return this; + + }, + + convertLinearToGamma: function () { + + this.r = Math.sqrt( this.r ); + this.g = Math.sqrt( this.g ); + this.b = Math.sqrt( this.b ); + + return this; + + }, + + getHex: function () { + + return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0; + + }, + + getHexString: function () { + + return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 ); + + }, + + getHSL: function ( optionalTarget ) { + + // h,s,l ranges are in 0.0 - 1.0 + + var hsl = optionalTarget || { h: 0, s: 0, l: 0 }; + + var r = this.r, g = this.g, b = this.b; + + var max = Math.max( r, g, b ); + var min = Math.min( r, g, b ); + + var hue, saturation; + var lightness = ( min + max ) / 2.0; + + if ( min === max ) { + + hue = 0; + saturation = 0; + + } else { + + var delta = max - min; + + saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min ); + + switch ( max ) { + + case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break; + case g: hue = ( b - r ) / delta + 2; break; + case b: hue = ( r - g ) / delta + 4; break; + + } + + hue /= 6; + + } + + hsl.h = hue; + hsl.s = saturation; + hsl.l = lightness; + + return hsl; + + }, + + getStyle: function () { + + return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')'; + + }, + + offsetHSL: function ( h, s, l ) { + + var hsl = this.getHSL(); + + hsl.h += h; hsl.s += s; hsl.l += l; + + this.setHSL( hsl.h, hsl.s, hsl.l ); + + return this; + + }, + + add: function ( color ) { + + this.r += color.r; + this.g += color.g; + this.b += color.b; + + return this; + + }, + + addColors: function ( color1, color2 ) { + + this.r = color1.r + color2.r; + this.g = color1.g + color2.g; + this.b = color1.b + color2.b; + + return this; + + }, + + addScalar: function ( s ) { + + this.r += s; + this.g += s; + this.b += s; + + return this; + + }, + + sub: function ( color ) { + + this.r = Math.max( 0, this.r - color.r ); + this.g = Math.max( 0, this.g - color.g ); + this.b = Math.max( 0, this.b - color.b ); + + return this; + + }, + + multiply: function ( color ) { + + this.r *= color.r; + this.g *= color.g; + this.b *= color.b; + + return this; + + }, + + multiplyScalar: function ( s ) { + + this.r *= s; + this.g *= s; + this.b *= s; + + return this; + + }, + + lerp: function ( color, alpha ) { + + this.r += ( color.r - this.r ) * alpha; + this.g += ( color.g - this.g ) * alpha; + this.b += ( color.b - this.b ) * alpha; + + return this; + + }, + + equals: function ( c ) { + + return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b ); + + }, + + fromArray: function ( array, offset ) { + + if ( offset === undefined ) offset = 0; + + this.r = array[ offset ]; + this.g = array[ offset + 1 ]; + this.b = array[ offset + 2 ]; + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this.r; + array[ offset + 1 ] = this.g; + array[ offset + 2 ] = this.b; + + return array; + + }, + + toJSON: function () { + + return this.getHex(); + + } + + } ); + + /** + * Uniforms library for shared webgl shaders + */ + + var UniformsLib = { + + common: { + + diffuse: { value: new Color( 0xeeeeee ) }, + opacity: { value: 1.0 }, + + map: { value: null }, + uvTransform: { value: new Matrix3() }, + + alphaMap: { value: null }, + + }, + + specularmap: { + + specularMap: { value: null }, + + }, + + envmap: { + + envMap: { value: null }, + flipEnvMap: { value: - 1 }, + reflectivity: { value: 1.0 }, + refractionRatio: { value: 0.98 } + + }, + + aomap: { + + aoMap: { value: null }, + aoMapIntensity: { value: 1 } + + }, + + lightmap: { + + lightMap: { value: null }, + lightMapIntensity: { value: 1 } + + }, + + emissivemap: { + + emissiveMap: { value: null } + + }, + + bumpmap: { + + bumpMap: { value: null }, + bumpScale: { value: 1 } + + }, + + normalmap: { + + normalMap: { value: null }, + normalScale: { value: new Vector2( 1, 1 ) } + + }, + + displacementmap: { + + displacementMap: { value: null }, + displacementScale: { value: 1 }, + displacementBias: { value: 0 } + + }, + + roughnessmap: { + + roughnessMap: { value: null } + + }, + + metalnessmap: { + + metalnessMap: { value: null } + + }, + + gradientmap: { + + gradientMap: { value: null } + + }, + + fog: { + + fogDensity: { value: 0.00025 }, + fogNear: { value: 1 }, + fogFar: { value: 2000 }, + fogColor: { value: new Color( 0xffffff ) } + + }, + + lights: { + + ambientLightColor: { value: [] }, + + directionalLights: { value: [], properties: { + direction: {}, + color: {}, + + shadow: {}, + shadowBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, + + directionalShadowMap: { value: [] }, + directionalShadowMatrix: { value: [] }, + + spotLights: { value: [], properties: { + color: {}, + position: {}, + direction: {}, + distance: {}, + coneCos: {}, + penumbraCos: {}, + decay: {}, + + shadow: {}, + shadowBias: {}, + shadowRadius: {}, + shadowMapSize: {} + } }, + + spotShadowMap: { value: [] }, + spotShadowMatrix: { value: [] }, + + pointLights: { value: [], properties: { + color: {}, + position: {}, + decay: {}, + distance: {}, + + shadow: {}, + shadowBias: {}, + shadowRadius: {}, + shadowMapSize: {}, + shadowCameraNear: {}, + shadowCameraFar: {} + } }, + + pointShadowMap: { value: [] }, + pointShadowMatrix: { value: [] }, + + hemisphereLights: { value: [], properties: { + direction: {}, + skyColor: {}, + groundColor: {} + } }, + + // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src + rectAreaLights: { value: [], properties: { + color: {}, + position: {}, + width: {}, + height: {} + } } + + }, + + points: { + + diffuse: { value: new Color( 0xeeeeee ) }, + opacity: { value: 1.0 }, + size: { value: 1.0 }, + scale: { value: 1.0 }, + map: { value: null }, + uvTransform: { value: new Matrix3() } + + } + + }; + + /** + * Uniform Utilities + */ + + var UniformsUtils = { + + merge: function ( uniforms ) { + + var merged = {}; + + for ( var u = 0; u < uniforms.length; u ++ ) { + + var tmp = this.clone( uniforms[ u ] ); + + for ( var p in tmp ) { + + merged[ p ] = tmp[ p ]; + + } + + } + + return merged; + + }, + + clone: function ( uniforms_src ) { + + var uniforms_dst = {}; + + for ( var u in uniforms_src ) { + + uniforms_dst[ u ] = {}; + + for ( var p in uniforms_src[ u ] ) { + + var parameter_src = uniforms_src[ u ][ p ]; + + if ( parameter_src && ( parameter_src.isColor || + parameter_src.isMatrix3 || parameter_src.isMatrix4 || + parameter_src.isVector2 || parameter_src.isVector3 || parameter_src.isVector4 || + parameter_src.isTexture ) ) { + + uniforms_dst[ u ][ p ] = parameter_src.clone(); + + } else if ( Array.isArray( parameter_src ) ) { + + uniforms_dst[ u ][ p ] = parameter_src.slice(); + + } else { + + uniforms_dst[ u ][ p ] = parameter_src; + + } + + } + + } + + return uniforms_dst; + + } + + }; + + var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif\n"; + + var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif\n"; + + var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif\n"; + + var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif\n"; + + var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif"; + + var begin_vertex = "\nvec3 transformed = vec3( position );\n"; + + var beginnormal_vertex = "\nvec3 objectNormal = vec3( normal );\n"; + + var bsdfs = "float punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\tif( decayExponent > 0.0 ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tfloat maxDistanceCutoffFactor = pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\treturn distanceFalloff * maxDistanceCutoffFactor;\n#else\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n#endif\n\t}\n\treturn 1.0;\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNL = saturate( dot( geometry.normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\tvec2 AB = vec2( -1.04, 1.04 ) * a004 + r.zw;\n\treturn specularColor * AB.x + AB.y;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n"; + + var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif\n"; + + var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vViewPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vViewPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\tif ( clipped ) discard;\n\t#endif\n#endif\n"; + + var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\t#if ! defined( PHYSICAL ) && ! defined( PHONG )\n\t\tvarying vec3 vViewPosition;\n\t#endif\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif\n"; + + var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvarying vec3 vViewPosition;\n#endif\n"; + + var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n"; + + var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif"; + + var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif\n"; + + var color_pars_vertex = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif"; + + var color_vertex = "#ifdef USE_COLOR\n\tvColor.xyz = color.xyz;\n#endif"; + + var common = "#define PI 3.14159265359\n#define PI2 6.28318530718\n#define PI_HALF 1.5707963267949\n#define RECIPROCAL_PI 0.31830988618\n#define RECIPROCAL_PI2 0.15915494\n#define LOG2 1.442695\n#define EPSILON 1e-6\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#define whiteCompliment(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\n"; + + var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n#define cubeUV_textureSize (1024.0)\nint getFaceFromDirection(vec3 direction) {\n\tvec3 absDirection = abs(direction);\n\tint face = -1;\n\tif( absDirection.x > absDirection.z ) {\n\t\tif(absDirection.x > absDirection.y )\n\t\t\tface = direction.x > 0.0 ? 0 : 3;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\telse {\n\t\tif(absDirection.z > absDirection.y )\n\t\t\tface = direction.z > 0.0 ? 2 : 5;\n\t\telse\n\t\t\tface = direction.y > 0.0 ? 1 : 4;\n\t}\n\treturn face;\n}\n#define cubeUV_maxLods1 (log2(cubeUV_textureSize*0.25) - 1.0)\n#define cubeUV_rangeClamp (exp2((6.0 - 1.0) * 2.0))\nvec2 MipLevelInfo( vec3 vec, float roughnessLevel, float roughness ) {\n\tfloat scale = exp2(cubeUV_maxLods1 - roughnessLevel);\n\tfloat dxRoughness = dFdx(roughness);\n\tfloat dyRoughness = dFdy(roughness);\n\tvec3 dx = dFdx( vec * scale * dxRoughness );\n\tvec3 dy = dFdy( vec * scale * dyRoughness );\n\tfloat d = max( dot( dx, dx ), dot( dy, dy ) );\n\td = clamp(d, 1.0, cubeUV_rangeClamp);\n\tfloat mipLevel = 0.5 * log2(d);\n\treturn vec2(floor(mipLevel), fract(mipLevel));\n}\n#define cubeUV_maxLods2 (log2(cubeUV_textureSize*0.25) - 2.0)\n#define cubeUV_rcpTextureSize (1.0 / cubeUV_textureSize)\nvec2 getCubeUV(vec3 direction, float roughnessLevel, float mipLevel) {\n\tmipLevel = roughnessLevel > cubeUV_maxLods2 - 3.0 ? 0.0 : mipLevel;\n\tfloat a = 16.0 * cubeUV_rcpTextureSize;\n\tvec2 exp2_packed = exp2( vec2( roughnessLevel, mipLevel ) );\n\tvec2 rcp_exp2_packed = vec2( 1.0 ) / exp2_packed;\n\tfloat powScale = exp2_packed.x * exp2_packed.y;\n\tfloat scale = rcp_exp2_packed.x * rcp_exp2_packed.y * 0.25;\n\tfloat mipOffset = 0.75*(1.0 - rcp_exp2_packed.y) * rcp_exp2_packed.x;\n\tbool bRes = mipLevel == 0.0;\n\tscale = bRes && (scale < a) ? a : scale;\n\tvec3 r;\n\tvec2 offset;\n\tint face = getFaceFromDirection(direction);\n\tfloat rcpPowScale = 1.0 / powScale;\n\tif( face == 0) {\n\t\tr = vec3(direction.x, -direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 1) {\n\t\tr = vec3(direction.y, direction.x, direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 2) {\n\t\tr = vec3(direction.z, direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.75 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? a : offset.y;\n\t}\n\telse if( face == 3) {\n\t\tr = vec3(direction.x, direction.z, direction.y);\n\t\toffset = vec2(0.0+mipOffset,0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse if( face == 4) {\n\t\tr = vec3(direction.y, direction.x, -direction.z);\n\t\toffset = vec2(scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\telse {\n\t\tr = vec3(direction.z, -direction.x, direction.y);\n\t\toffset = vec2(2.0*scale+mipOffset, 0.5 * rcpPowScale);\n\t\toffset.y = bRes && (offset.y < 2.0*a) ? 0.0 : offset.y;\n\t}\n\tr = normalize(r);\n\tfloat texelOffset = 0.5 * cubeUV_rcpTextureSize;\n\tvec2 s = ( r.yz / abs( r.x ) + vec2( 1.0 ) ) * 0.5;\n\tvec2 base = offset + vec2( texelOffset );\n\treturn base + s * ( scale - 2.0 * texelOffset );\n}\n#define cubeUV_maxLods3 (log2(cubeUV_textureSize*0.25) - 3.0)\nvec4 textureCubeUV(vec3 reflectedDirection, float roughness ) {\n\tfloat roughnessVal = roughness* cubeUV_maxLods3;\n\tfloat r1 = floor(roughnessVal);\n\tfloat r2 = r1 + 1.0;\n\tfloat t = fract(roughnessVal);\n\tvec2 mipInfo = MipLevelInfo(reflectedDirection, r1, roughness);\n\tfloat s = mipInfo.y;\n\tfloat level0 = mipInfo.x;\n\tfloat level1 = level0 + 1.0;\n\tlevel1 = level1 > 5.0 ? 5.0 : level1;\n\tlevel0 += min( floor( s + 0.5 ), 5.0 );\n\tvec2 uv_10 = getCubeUV(reflectedDirection, r1, level0);\n\tvec4 color10 = envMapTexelToLinear(texture2D(envMap, uv_10));\n\tvec2 uv_20 = getCubeUV(reflectedDirection, r2, level0);\n\tvec4 color20 = envMapTexelToLinear(texture2D(envMap, uv_20));\n\tvec4 result = mix(color10, color20, t);\n\treturn vec4(result.rgb, 1.0);\n}\n#endif\n"; + + var defaultnormal_vertex = "vec3 transformedNormal = normalMatrix * objectNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n"; + + var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif\n"; + + var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, uv ).x * displacementScale + displacementBias );\n#endif\n"; + + var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif\n"; + + var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif\n"; + + var encodings_fragment = " gl_FragColor = linearToOutputTexel( gl_FragColor );\n"; + + var encodings_pars_fragment = "\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( gammaFactor ) ), value.w );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.xyz, vec3( 1.0 / gammaFactor ) ), value.w );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.w );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.w );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.xyz * value.w * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.x, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = min( floor( D ) / 255.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = value.rgb * cLogLuvM;\n\tXp_Y_XYZp = max(Xp_Y_XYZp, vec3(1e-6, 1e-6, 1e-6));\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract(Le);\n\tvResult.z = (Le - (floor(vResult.w*255.0))/255.0)/255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2((Le - 127.0) / 2.0);\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = Xp_Y_XYZp.rgb * cLogLuvInverseM;\n\treturn vec4( max(vRGB, 0.0), 1.0 );\n}\n"; + + var envmap_fragment = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\tvec2 sampleUV;\n\t\treflectVec = normalize( reflectVec );\n\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\tvec4 envColor = texture2D( envMap, sampleUV );\n\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\treflectVec = normalize( reflectVec );\n\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0, 0.0, 1.0 ) );\n\t\tvec4 envColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\tenvColor = envMapTexelToLinear( envColor );\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif\n"; + + var envmap_pars_fragment = "#if defined( USE_ENVMAP ) || defined( PHYSICAL )\n\tuniform float reflectivity;\n\tuniform float envMapIntensity;\n#endif\n#ifdef USE_ENVMAP\n\t#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )\n\t\tvarying vec3 vWorldPosition;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\tuniform float flipEnvMap;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif\n"; + + var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif\n"; + + var envmap_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif\n"; + + var fog_vertex = "\n#ifdef USE_FOG\nfogDepth = -mvPosition.z;\n#endif"; + + var fog_pars_vertex = "#ifdef USE_FOG\n varying float fogDepth;\n#endif\n"; + + var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = whiteCompliment( exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 ) );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif\n"; + + var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif\n"; + + var gradientmap_pars_fragment = "#ifdef TOON\n\tuniform sampler2D gradientMap;\n\tvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\t\tfloat dotNL = dot( normal, lightDirection );\n\t\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t\t#ifdef USE_GRADIENTMAP\n\t\t\treturn texture2D( gradientMap, coord ).rgb;\n\t\t#else\n\t\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t\t#endif\n\t}\n#endif\n"; + + var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\treflectedLight.indirectDiffuse += PI * texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n#endif\n"; + + var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif"; + + var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvLightFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n#endif\n"; + + var lights_pars_begin = "uniform vec3 ambientLightColor;\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t\tfloat shadowCameraNear;\n\t\tfloat shadowCameraFar;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t\tint shadow;\n\t\tfloat shadowBias;\n\t\tfloat shadowRadius;\n\t\tvec2 shadowMapSize;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif\n"; + + var lights_pars_maps = "#if defined( USE_ENVMAP ) && defined( PHYSICAL )\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\tvec4 envMapColor = textureCubeUV( queryVec, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + 0.79248 - 0.5 * log2( pow2( blinnShininessExponent ) + 1.0 );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in GeometricContext geometry, const in float blinnShininessExponent, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -geometry.viewDir, geometry.normal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -geometry.viewDir, geometry.normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( blinnShininessExponent, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\tvec4 envMapColor = textureCubeUV(queryReflectVec, BlinnExponentToGGXRoughness(blinnShininessExponent));\n\t\t#elif defined( ENVMAP_TYPE_EQUIREC )\n\t\t\tvec2 sampleUV;\n\t\t\tsampleUV.y = asin( clamp( reflectVec.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\t\t\tsampleUV.x = atan( reflectVec.z, reflectVec.x ) * RECIPROCAL_PI2 + 0.5;\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, sampleUV, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, sampleUV, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_SPHERE )\n\t\t\tvec3 reflectView = normalize( ( viewMatrix * vec4( reflectVec, 0.0 ) ).xyz + vec3( 0.0,0.0,1.0 ) );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = texture2DLodEXT( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = texture2D( envMap, reflectView.xy * 0.5 + 0.5, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif\n"; + + var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;\n"; + + var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3\tdiffuseColor;\n\tvec3\tspecularColor;\n\tfloat\tspecularShininess;\n\tfloat\tspecularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifdef TOON\n\t\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#else\n\t\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\t\tvec3 irradiance = dotNL * directLight.color;\n\t#endif\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)\n"; + + var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nmaterial.specularRoughness = clamp( roughnessFactor, 0.04, 1.0 );\n#ifdef STANDARD\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.clearCoat = saturate( clearCoat );\tmaterial.clearCoatRoughness = clamp( clearCoatRoughness, 0.04, 1.0 );\n#endif\n"; + + var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3\tdiffuseColor;\n\tfloat\tspecularRoughness;\n\tvec3\tspecularColor;\n\t#ifndef STANDARD\n\t\tfloat clearCoat;\n\t\tfloat clearCoatRoughness;\n\t#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearCoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos - halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos + halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos + halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos - halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifndef STANDARD\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.directSpecular += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry, material.specularColor, material.specularRoughness );\n\treflectedLight.directDiffuse += ( 1.0 - clearCoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\t#ifndef STANDARD\n\t\treflectedLight.directSpecular += irradiance * material.clearCoat * BRDF_Specular_GGX( directLight, geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 clearCoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t#ifndef STANDARD\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\tfloat dotNL = dotNV;\n\t\tfloat clearCoatDHR = material.clearCoat * clearCoatDHRApprox( material.clearCoatRoughness, dotNL );\n\t#else\n\t\tfloat clearCoatDHR = 0.0;\n\t#endif\n\treflectedLight.indirectSpecular += ( 1.0 - clearCoatDHR ) * radiance * BRDF_Specular_GGX_Environment( geometry, material.specularColor, material.specularRoughness );\n\t#ifndef STANDARD\n\t\treflectedLight.indirectSpecular += clearCoatRadiance * material.clearCoat * BRDF_Specular_GGX_Environment( geometry, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearCoatRoughness );\n\t#endif\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\n#define Material_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.specularRoughness )\n#define Material_ClearCoat_BlinnShininessExponent( material ) GGXRoughnessToBlinnExponent( material.clearCoatRoughness )\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}\n"; + + var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = normalize( vViewPosition );\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( pointLight.shadow, directLight.visible ) ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( spotLight.shadow, directLight.visible ) ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#ifdef USE_SHADOWMAP\n\t\tdirectLight.color *= all( bvec2( directionalLight.shadow, directLight.visible ) ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearCoatRadiance = vec3( 0.0 );\n#endif\n"; + + var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec3 lightMapIrradiance = texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( PHYSICAL ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tirradiance += getLightProbeIndirectIrradiance( geometry, 8 );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry, Material_BlinnShininessExponent( material ), 8 );\n\t#ifndef STANDARD\n\t\tclearCoatRadiance += getLightProbeIndirectRadiance( geometry, Material_ClearCoat_BlinnShininessExponent( material ), 8 );\n\t#endif\n#endif\n"; + + var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, clearCoatRadiance, geometry, material, reflectedLight );\n#endif\n"; + + var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif"; + + var logdepthbuf_pars_fragment = "#ifdef USE_LOGDEPTHBUF\n\tuniform float logDepthBufFC;\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n#endif\n"; + + var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t#endif\n\tuniform float logDepthBufFC;\n#endif"; + + var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t#else\n\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\tgl_Position.z *= gl_Position.w;\n\t#endif\n#endif\n"; + + var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif\n"; + + var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n"; + + var map_particle_fragment = "#ifdef USE_MAP\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n"; + + var map_particle_pars_fragment = "#ifdef USE_MAP\n\tuniform mat3 uvTransform;\n\tuniform sampler2D map;\n#endif\n"; + + var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif\n"; + + var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif"; + + var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\n\tobjectNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\n\tobjectNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\n\tobjectNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\n#endif\n"; + + var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\t#ifndef USE_MORPHNORMALS\n\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif"; + + var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\n\ttransformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\n\ttransformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\n\ttransformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\ttransformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\n\ttransformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\n\ttransformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\n\ttransformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n\t#endif\n#endif\n"; + + var normal_fragment_begin = "#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n#endif\n"; + + var normal_fragment_maps = "#ifdef USE_NORMALMAP\n\tnormal = perturbNormal2Arb( -vViewPosition, normal );\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n"; + + var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tvec3 S = normalize( q0 * st1.t - q1 * st0.t );\n\t\tvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\n\t\tvec3 N = normalize( surf_norm );\n\t\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t\tmapN.xy = normalScale * mapN.xy;\n\t\tmat3 tsn = mat3( S, T, N );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif\n"; + + var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}\n"; + + var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif\n"; + + var project_vertex = "vec4 mvPosition = modelViewMatrix * vec4( transformed, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\n"; + + var dithering_fragment = "#if defined( DITHERING )\n gl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif\n"; + + var dithering_pars_fragment = "#if defined( DITHERING )\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif\n"; + + var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif\n"; + + var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif"; + + var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tfloat texture2DShadowLerp( sampler2D depths, vec2 size, vec2 uv, float compare ) {\n\t\tconst vec2 offset = vec2( 0.0, 1.0 );\n\t\tvec2 texelSize = vec2( 1.0 ) / size;\n\t\tvec2 centroidUV = floor( uv * size + 0.5 ) / size;\n\t\tfloat lb = texture2DCompare( depths, centroidUV + texelSize * offset.xx, compare );\n\t\tfloat lt = texture2DCompare( depths, centroidUV + texelSize * offset.xy, compare );\n\t\tfloat rb = texture2DCompare( depths, centroidUV + texelSize * offset.yx, compare );\n\t\tfloat rt = texture2DCompare( depths, centroidUV + texelSize * offset.yy, compare );\n\t\tvec2 f = fract( uv * size + 0.5 );\n\t\tfloat a = mix( lb, lt, f.y );\n\t\tfloat b = mix( rb, rt, f.y );\n\t\tfloat c = mix( a, b, f.x );\n\t\treturn c;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tshadow = (\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DShadowLerp( shadowMap, shadowMapSize, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif\n"; + + var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHTS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHTS ];\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHTS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHTS ];\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHTS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHTS ];\n\t#endif\n#endif\n"; + + var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * worldPosition;\n\t}\n\t#endif\n#endif\n"; + + var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHTS > 0\n\tDirectionalLight directionalLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tshadow *= bool( directionalLight.shadow ) ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_SPOT_LIGHTS > 0\n\tSpotLight spotLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tshadow *= bool( spotLight.shadow ) ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#endif\n\t#if NUM_POINT_LIGHTS > 0\n\tPointLight pointLight;\n\t#pragma unroll_loop\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tshadow *= bool( pointLight.shadow ) ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#endif\n\t#endif\n\treturn shadow;\n}\n"; + + var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif"; + + var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif\n"; + + var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif\n"; + + var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n#endif\n"; + + var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif"; + + var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif"; + + var tonemapping_fragment = "#if defined( TONE_MAPPING )\n gl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif\n"; + + var tonemapping_pars_fragment = "#ifndef saturate\n\t#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nuniform float toneMappingWhitePoint;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\n#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )\nvec3 Uncharted2ToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( toneMappingWhitePoint ) ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\n"; + + var uv_pars_fragment = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n#endif"; + + var uv_pars_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvarying vec2 vUv;\n\tuniform mat3 uvTransform;\n#endif\n"; + + var uv_vertex = "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP ) || defined( USE_ALPHAMAP ) || defined( USE_EMISSIVEMAP ) || defined( USE_ROUGHNESSMAP ) || defined( USE_METALNESSMAP )\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif"; + + var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif"; + + var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n#endif"; + + var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = uv2;\n#endif"; + + var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );\n#endif\n"; + + var cube_frag = "uniform samplerCube tCube;\nuniform float tFlip;\nuniform float opacity;\nvarying vec3 vWorldPosition;\nvoid main() {\n\tgl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n\tgl_FragColor.a *= opacity;\n}\n"; + + var cube_vert = "varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n\tgl_Position.z = gl_Position.w;\n}\n"; + + var depth_frag = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( gl_FragCoord.z ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( gl_FragCoord.z );\n\t#endif\n}\n"; + + var depth_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var distanceRGBA_frag = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main () {\n\t#include \n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include \n\t#include \n\t#include \n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}\n"; + + var distanceRGBA_vert = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include \n\t\t#include \n\t\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvWorldPosition = worldPosition.xyz;\n}\n"; + + var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvec3 direction = normalize( vWorldPosition );\n\tvec2 sampleUV;\n\tsampleUV.y = asin( clamp( direction.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\tsampleUV.x = atan( direction.z, direction.x ) * RECIPROCAL_PI2 + 0.5;\n\tgl_FragColor = texture2D( tEquirect, sampleUV );\n}\n"; + + var equirect_vert = "varying vec3 vWorldPosition;\n#include \nvoid main() {\n\tvWorldPosition = transformDirection( position, modelMatrix );\n\t#include \n\t#include \n}\n"; + + var linedashed_frag = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvLineDistance = scale * lineDistance;\n\tvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include \n\t#include \n\t#include \n}\n"; + + var meshbasic_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\treflectedLight.indirectDiffuse += texture2D( lightMap, vUv2 ).xyz * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include \n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshbasic_vert = "#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#ifdef USE_ENVMAP\n\t#include \n\t#include \n\t#include \n\t#include \n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\treflectedLight.indirectDiffuse = getAmbientLightIrradiance( ambientLightColor );\n\t#include \n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshphong_frag = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include \n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshphong_vert = "#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshphysical_frag = "#define PHYSICAL\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifndef STANDARD\n\tuniform float clearCoat;\n\tuniform float clearCoatRoughness;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var meshphysical_vert = "#define PHYSICAL\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\tvViewPosition = - mvPosition.xyz;\n\t#include \n\t#include \n\t#include \n}\n"; + + var normal_frag = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}\n"; + + var normal_vert = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}\n"; + + var points_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include \n\t#include \n\t#include \n\t#include \n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var points_vert = "uniform float size;\nuniform float scale;\n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#ifdef USE_SIZEATTENUATION\n\t\tgl_PointSize = size * ( scale / - mvPosition.z );\n\t#else\n\t\tgl_PointSize = size;\n\t#endif\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var shadow_frag = "uniform vec3 color;\nuniform float opacity;\n#include \n#include \n#include \n#include \n#include \n#include \n#include \nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include \n}\n"; + + var shadow_vert = "#include \n#include \nvoid main() {\n\t#include \n\t#include \n\t#include \n\t#include \n\t#include \n}\n"; + + var ShaderChunk = { + alphamap_fragment: alphamap_fragment, + alphamap_pars_fragment: alphamap_pars_fragment, + alphatest_fragment: alphatest_fragment, + aomap_fragment: aomap_fragment, + aomap_pars_fragment: aomap_pars_fragment, + begin_vertex: begin_vertex, + beginnormal_vertex: beginnormal_vertex, + bsdfs: bsdfs, + bumpmap_pars_fragment: bumpmap_pars_fragment, + clipping_planes_fragment: clipping_planes_fragment, + clipping_planes_pars_fragment: clipping_planes_pars_fragment, + clipping_planes_pars_vertex: clipping_planes_pars_vertex, + clipping_planes_vertex: clipping_planes_vertex, + color_fragment: color_fragment, + color_pars_fragment: color_pars_fragment, + color_pars_vertex: color_pars_vertex, + color_vertex: color_vertex, + common: common, + cube_uv_reflection_fragment: cube_uv_reflection_fragment, + defaultnormal_vertex: defaultnormal_vertex, + displacementmap_pars_vertex: displacementmap_pars_vertex, + displacementmap_vertex: displacementmap_vertex, + emissivemap_fragment: emissivemap_fragment, + emissivemap_pars_fragment: emissivemap_pars_fragment, + encodings_fragment: encodings_fragment, + encodings_pars_fragment: encodings_pars_fragment, + envmap_fragment: envmap_fragment, + envmap_pars_fragment: envmap_pars_fragment, + envmap_pars_vertex: envmap_pars_vertex, + envmap_vertex: envmap_vertex, + fog_vertex: fog_vertex, + fog_pars_vertex: fog_pars_vertex, + fog_fragment: fog_fragment, + fog_pars_fragment: fog_pars_fragment, + gradientmap_pars_fragment: gradientmap_pars_fragment, + lightmap_fragment: lightmap_fragment, + lightmap_pars_fragment: lightmap_pars_fragment, + lights_lambert_vertex: lights_lambert_vertex, + lights_pars_begin: lights_pars_begin, + lights_pars_maps: lights_pars_maps, + lights_phong_fragment: lights_phong_fragment, + lights_phong_pars_fragment: lights_phong_pars_fragment, + lights_physical_fragment: lights_physical_fragment, + lights_physical_pars_fragment: lights_physical_pars_fragment, + lights_fragment_begin: lights_fragment_begin, + lights_fragment_maps: lights_fragment_maps, + lights_fragment_end: lights_fragment_end, + logdepthbuf_fragment: logdepthbuf_fragment, + logdepthbuf_pars_fragment: logdepthbuf_pars_fragment, + logdepthbuf_pars_vertex: logdepthbuf_pars_vertex, + logdepthbuf_vertex: logdepthbuf_vertex, + map_fragment: map_fragment, + map_pars_fragment: map_pars_fragment, + map_particle_fragment: map_particle_fragment, + map_particle_pars_fragment: map_particle_pars_fragment, + metalnessmap_fragment: metalnessmap_fragment, + metalnessmap_pars_fragment: metalnessmap_pars_fragment, + morphnormal_vertex: morphnormal_vertex, + morphtarget_pars_vertex: morphtarget_pars_vertex, + morphtarget_vertex: morphtarget_vertex, + normal_fragment_begin: normal_fragment_begin, + normal_fragment_maps: normal_fragment_maps, + normalmap_pars_fragment: normalmap_pars_fragment, + packing: packing, + premultiplied_alpha_fragment: premultiplied_alpha_fragment, + project_vertex: project_vertex, + dithering_fragment: dithering_fragment, + dithering_pars_fragment: dithering_pars_fragment, + roughnessmap_fragment: roughnessmap_fragment, + roughnessmap_pars_fragment: roughnessmap_pars_fragment, + shadowmap_pars_fragment: shadowmap_pars_fragment, + shadowmap_pars_vertex: shadowmap_pars_vertex, + shadowmap_vertex: shadowmap_vertex, + shadowmask_pars_fragment: shadowmask_pars_fragment, + skinbase_vertex: skinbase_vertex, + skinning_pars_vertex: skinning_pars_vertex, + skinning_vertex: skinning_vertex, + skinnormal_vertex: skinnormal_vertex, + specularmap_fragment: specularmap_fragment, + specularmap_pars_fragment: specularmap_pars_fragment, + tonemapping_fragment: tonemapping_fragment, + tonemapping_pars_fragment: tonemapping_pars_fragment, + uv_pars_fragment: uv_pars_fragment, + uv_pars_vertex: uv_pars_vertex, + uv_vertex: uv_vertex, + uv2_pars_fragment: uv2_pars_fragment, + uv2_pars_vertex: uv2_pars_vertex, + uv2_vertex: uv2_vertex, + worldpos_vertex: worldpos_vertex, + + cube_frag: cube_frag, + cube_vert: cube_vert, + depth_frag: depth_frag, + depth_vert: depth_vert, + distanceRGBA_frag: distanceRGBA_frag, + distanceRGBA_vert: distanceRGBA_vert, + equirect_frag: equirect_frag, + equirect_vert: equirect_vert, + linedashed_frag: linedashed_frag, + linedashed_vert: linedashed_vert, + meshbasic_frag: meshbasic_frag, + meshbasic_vert: meshbasic_vert, + meshlambert_frag: meshlambert_frag, + meshlambert_vert: meshlambert_vert, + meshphong_frag: meshphong_frag, + meshphong_vert: meshphong_vert, + meshphysical_frag: meshphysical_frag, + meshphysical_vert: meshphysical_vert, + normal_frag: normal_frag, + normal_vert: normal_vert, + points_frag: points_frag, + points_vert: points_vert, + shadow_frag: shadow_frag, + shadow_vert: shadow_vert + }; + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + */ + + var ShaderLib = { + + basic: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.fog + ] ), + + vertexShader: ShaderChunk.meshbasic_vert, + fragmentShader: ShaderChunk.meshbasic_frag + + }, + + lambert: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: new Color( 0x000000 ) } + } + ] ), + + vertexShader: ShaderChunk.meshlambert_vert, + fragmentShader: ShaderChunk.meshlambert_frag + + }, + + phong: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.specularmap, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.gradientmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: new Color( 0x000000 ) }, + specular: { value: new Color( 0x111111 ) }, + shininess: { value: 30 } + } + ] ), + + vertexShader: ShaderChunk.meshphong_vert, + fragmentShader: ShaderChunk.meshphong_frag + + }, + + standard: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.envmap, + UniformsLib.aomap, + UniformsLib.lightmap, + UniformsLib.emissivemap, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + UniformsLib.roughnessmap, + UniformsLib.metalnessmap, + UniformsLib.fog, + UniformsLib.lights, + { + emissive: { value: new Color( 0x000000 ) }, + roughness: { value: 0.5 }, + metalness: { value: 0.5 }, + envMapIntensity: { value: 1 } // temporary + } + ] ), + + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag + + }, + + points: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.points, + UniformsLib.fog + ] ), + + vertexShader: ShaderChunk.points_vert, + fragmentShader: ShaderChunk.points_frag + + }, + + dashed: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.fog, + { + scale: { value: 1 }, + dashSize: { value: 1 }, + totalSize: { value: 2 } + } + ] ), + + vertexShader: ShaderChunk.linedashed_vert, + fragmentShader: ShaderChunk.linedashed_frag + + }, + + depth: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.displacementmap + ] ), + + vertexShader: ShaderChunk.depth_vert, + fragmentShader: ShaderChunk.depth_frag + + }, + + normal: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.bumpmap, + UniformsLib.normalmap, + UniformsLib.displacementmap, + { + opacity: { value: 1.0 } + } + ] ), + + vertexShader: ShaderChunk.normal_vert, + fragmentShader: ShaderChunk.normal_frag + + }, + + /* ------------------------------------------------------------------------- + // Cube map shader + ------------------------------------------------------------------------- */ + + cube: { + + uniforms: { + tCube: { value: null }, + tFlip: { value: - 1 }, + opacity: { value: 1.0 } + }, + + vertexShader: ShaderChunk.cube_vert, + fragmentShader: ShaderChunk.cube_frag + + }, + + equirect: { + + uniforms: { + tEquirect: { value: null }, + }, + + vertexShader: ShaderChunk.equirect_vert, + fragmentShader: ShaderChunk.equirect_frag + + }, + + distanceRGBA: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.common, + UniformsLib.displacementmap, + { + referencePosition: { value: new Vector3() }, + nearDistance: { value: 1 }, + farDistance: { value: 1000 } + } + ] ), + + vertexShader: ShaderChunk.distanceRGBA_vert, + fragmentShader: ShaderChunk.distanceRGBA_frag + + }, + + shadow: { + + uniforms: UniformsUtils.merge( [ + UniformsLib.lights, + UniformsLib.fog, + { + color: { value: new Color( 0x00000 ) }, + opacity: { value: 1.0 } + }, + ] ), + + vertexShader: ShaderChunk.shadow_vert, + fragmentShader: ShaderChunk.shadow_frag + + } + + }; + + ShaderLib.physical = { + + uniforms: UniformsUtils.merge( [ + ShaderLib.standard.uniforms, + { + clearCoat: { value: 0 }, + clearCoatRoughness: { value: 0 } + } + ] ), + + vertexShader: ShaderChunk.meshphysical_vert, + fragmentShader: ShaderChunk.meshphysical_frag + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.needsUpdate = true; + + } + + CanvasTexture.prototype = Object.create( Texture.prototype ); + CanvasTexture.prototype.constructor = CanvasTexture; + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + + function WebGLSpriteRenderer( renderer, gl, state, textures, capabilities ) { + + var vertexBuffer, elementBuffer; + var program, attributes, uniforms; + + var texture; + + // decompose matrixWorld + + var spritePosition = new Vector3(); + var spriteRotation = new Quaternion(); + var spriteScale = new Vector3(); + + function init() { + + var vertices = new Float32Array( [ + - 0.5, - 0.5, 0, 0, + 0.5, - 0.5, 1, 0, + 0.5, 0.5, 1, 1, + - 0.5, 0.5, 0, 1 + ] ); + + var faces = new Uint16Array( [ + 0, 1, 2, + 0, 2, 3 + ] ); + + vertexBuffer = gl.createBuffer(); + elementBuffer = gl.createBuffer(); + + gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); + gl.bufferData( gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW ); + + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + gl.bufferData( gl.ELEMENT_ARRAY_BUFFER, faces, gl.STATIC_DRAW ); + + program = createProgram(); + + attributes = { + position: gl.getAttribLocation( program, 'position' ), + uv: gl.getAttribLocation( program, 'uv' ) + }; + + uniforms = { + uvOffset: gl.getUniformLocation( program, 'uvOffset' ), + uvScale: gl.getUniformLocation( program, 'uvScale' ), + + rotation: gl.getUniformLocation( program, 'rotation' ), + center: gl.getUniformLocation( program, 'center' ), + scale: gl.getUniformLocation( program, 'scale' ), + + color: gl.getUniformLocation( program, 'color' ), + map: gl.getUniformLocation( program, 'map' ), + opacity: gl.getUniformLocation( program, 'opacity' ), + + modelViewMatrix: gl.getUniformLocation( program, 'modelViewMatrix' ), + projectionMatrix: gl.getUniformLocation( program, 'projectionMatrix' ), + + fogType: gl.getUniformLocation( program, 'fogType' ), + fogDensity: gl.getUniformLocation( program, 'fogDensity' ), + fogNear: gl.getUniformLocation( program, 'fogNear' ), + fogFar: gl.getUniformLocation( program, 'fogFar' ), + fogColor: gl.getUniformLocation( program, 'fogColor' ), + fogDepth: gl.getUniformLocation( program, 'fogDepth' ), + + alphaTest: gl.getUniformLocation( program, 'alphaTest' ) + }; + + var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); + canvas.width = 8; + canvas.height = 8; + + var context = canvas.getContext( '2d' ); + context.fillStyle = 'white'; + context.fillRect( 0, 0, 8, 8 ); + + texture = new CanvasTexture( canvas ); + + } + + this.render = function ( sprites, scene, camera ) { + + if ( sprites.length === 0 ) return; + + // setup gl + + if ( program === undefined ) { + + init(); + + } + + state.useProgram( program ); + + state.initAttributes(); + state.enableAttribute( attributes.position ); + state.enableAttribute( attributes.uv ); + state.disableUnusedAttributes(); + + state.disable( gl.CULL_FACE ); + state.enable( gl.BLEND ); + + gl.bindBuffer( gl.ARRAY_BUFFER, vertexBuffer ); + gl.vertexAttribPointer( attributes.position, 2, gl.FLOAT, false, 2 * 8, 0 ); + gl.vertexAttribPointer( attributes.uv, 2, gl.FLOAT, false, 2 * 8, 8 ); + + gl.bindBuffer( gl.ELEMENT_ARRAY_BUFFER, elementBuffer ); + + gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); + + state.activeTexture( gl.TEXTURE0 ); + gl.uniform1i( uniforms.map, 0 ); + + var oldFogType = 0; + var sceneFogType = 0; + var fog = scene.fog; + + if ( fog ) { + + gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b ); + + if ( fog.isFog ) { + + gl.uniform1f( uniforms.fogNear, fog.near ); + gl.uniform1f( uniforms.fogFar, fog.far ); + + gl.uniform1i( uniforms.fogType, 1 ); + oldFogType = 1; + sceneFogType = 1; + + } else if ( fog.isFogExp2 ) { + + gl.uniform1f( uniforms.fogDensity, fog.density ); + + gl.uniform1i( uniforms.fogType, 2 ); + oldFogType = 2; + sceneFogType = 2; + + } + + } else { + + gl.uniform1i( uniforms.fogType, 0 ); + oldFogType = 0; + sceneFogType = 0; + + } + + + // update positions and sort + + for ( var i = 0, l = sprites.length; i < l; i ++ ) { + + var sprite = sprites[ i ]; + + sprite.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld ); + sprite.z = - sprite.modelViewMatrix.elements[ 14 ]; + + } + + sprites.sort( painterSortStable ); + + // render all sprites + + var scale = []; + var center = []; + + for ( var i = 0, l = sprites.length; i < l; i ++ ) { + + var sprite = sprites[ i ]; + var material = sprite.material; + + if ( material.visible === false ) continue; + + sprite.onBeforeRender( renderer, scene, camera, undefined, material, undefined ); + + gl.uniform1f( uniforms.alphaTest, material.alphaTest ); + gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite.modelViewMatrix.elements ); + + sprite.matrixWorld.decompose( spritePosition, spriteRotation, spriteScale ); + + scale[ 0 ] = spriteScale.x; + scale[ 1 ] = spriteScale.y; + + center[ 0 ] = sprite.center.x - 0.5; + center[ 1 ] = sprite.center.y - 0.5; + + var fogType = 0; + + if ( scene.fog && material.fog ) { + + fogType = sceneFogType; + + } + + if ( oldFogType !== fogType ) { + + gl.uniform1i( uniforms.fogType, fogType ); + oldFogType = fogType; + + } + + if ( material.map !== null ) { + + gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y ); + gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y ); + + } else { + + gl.uniform2f( uniforms.uvOffset, 0, 0 ); + gl.uniform2f( uniforms.uvScale, 1, 1 ); + + } + + gl.uniform1f( uniforms.opacity, material.opacity ); + gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b ); + + gl.uniform1f( uniforms.rotation, material.rotation ); + gl.uniform2fv( uniforms.center, center ); + gl.uniform2fv( uniforms.scale, scale ); + + state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ); + state.buffers.depth.setTest( material.depthTest ); + state.buffers.depth.setMask( material.depthWrite ); + state.buffers.color.setMask( material.colorWrite ); + + textures.setTexture2D( material.map || texture, 0 ); + + gl.drawElements( gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0 ); + + sprite.onAfterRender( renderer, scene, camera, undefined, material, undefined ); + + } + + // restore gl + + state.enable( gl.CULL_FACE ); + + state.reset(); + + }; + + function createProgram() { + + var program = gl.createProgram(); + + var vertexShader = gl.createShader( gl.VERTEX_SHADER ); + var fragmentShader = gl.createShader( gl.FRAGMENT_SHADER ); + + gl.shaderSource( vertexShader, [ + + 'precision ' + capabilities.precision + ' float;', + + '#define SHADER_NAME ' + 'SpriteMaterial', + + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform float rotation;', + 'uniform vec2 center;', + 'uniform vec2 scale;', + 'uniform vec2 uvOffset;', + 'uniform vec2 uvScale;', + + 'attribute vec2 position;', + 'attribute vec2 uv;', + + 'varying vec2 vUV;', + 'varying float fogDepth;', + + 'void main() {', + + ' vUV = uvOffset + uv * uvScale;', + + ' vec2 alignedPosition = ( position - center ) * scale;', + + ' vec2 rotatedPosition;', + ' rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;', + ' rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;', + + ' vec4 mvPosition;', + + ' mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );', + ' mvPosition.xy += rotatedPosition;', + + ' gl_Position = projectionMatrix * mvPosition;', + + ' fogDepth = - mvPosition.z;', + + '}' + + ].join( '\n' ) ); + + gl.shaderSource( fragmentShader, [ + + 'precision ' + capabilities.precision + ' float;', + + '#define SHADER_NAME ' + 'SpriteMaterial', + + 'uniform vec3 color;', + 'uniform sampler2D map;', + 'uniform float opacity;', + + 'uniform int fogType;', + 'uniform vec3 fogColor;', + 'uniform float fogDensity;', + 'uniform float fogNear;', + 'uniform float fogFar;', + 'uniform float alphaTest;', + + 'varying vec2 vUV;', + 'varying float fogDepth;', + + 'void main() {', + + ' vec4 texture = texture2D( map, vUV );', + + ' gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );', + + ' if ( gl_FragColor.a < alphaTest ) discard;', + + ' if ( fogType > 0 ) {', + + ' float fogFactor = 0.0;', + + ' if ( fogType == 1 ) {', + + ' fogFactor = smoothstep( fogNear, fogFar, fogDepth );', + + ' } else {', + + ' const float LOG2 = 1.442695;', + ' fogFactor = exp2( - fogDensity * fogDensity * fogDepth * fogDepth * LOG2 );', + ' fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );', + + ' }', + + ' gl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );', + + ' }', + + '}' + + ].join( '\n' ) ); + + gl.compileShader( vertexShader ); + gl.compileShader( fragmentShader ); + + gl.attachShader( program, vertexShader ); + gl.attachShader( program, fragmentShader ); + + gl.linkProgram( program ); + + return program; + + } + + function painterSortStable( a, b ) { + + if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return b.id - a.id; + + } + + } + + } + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + var materialId = 0; + + function Material() { + + Object.defineProperty( this, 'id', { value: materialId ++ } ); + + this.uuid = _Math.generateUUID(); + + this.name = ''; + this.type = 'Material'; + + this.fog = true; + this.lights = true; + + this.blending = NormalBlending; + this.side = FrontSide; + this.flatShading = false; + this.vertexColors = NoColors; // THREE.NoColors, THREE.VertexColors, THREE.FaceColors + + this.opacity = 1; + this.transparent = false; + + this.blendSrc = SrcAlphaFactor; + this.blendDst = OneMinusSrcAlphaFactor; + this.blendEquation = AddEquation; + this.blendSrcAlpha = null; + this.blendDstAlpha = null; + this.blendEquationAlpha = null; + + this.depthFunc = LessEqualDepth; + this.depthTest = true; + this.depthWrite = true; + + this.clippingPlanes = null; + this.clipIntersection = false; + this.clipShadows = false; + + this.shadowSide = null; + + this.colorWrite = true; + + this.precision = null; // override the renderer's default precision for this material + + this.polygonOffset = false; + this.polygonOffsetFactor = 0; + this.polygonOffsetUnits = 0; + + this.dithering = false; + + this.alphaTest = 0; + this.premultipliedAlpha = false; + + this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer + + this.visible = true; + + this.userData = {}; + + this.needsUpdate = true; + + } + + Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Material, + + isMaterial: true, + + onBeforeCompile: function () {}, + + setValues: function ( values ) { + + if ( values === undefined ) return; + + for ( var key in values ) { + + var newValue = values[ key ]; + + if ( newValue === undefined ) { + + console.warn( "THREE.Material: '" + key + "' parameter is undefined." ); + continue; + + } + + // for backward compatability if shading is set in the constructor + if ( key === 'shading' ) { + + console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); + this.flatShading = ( newValue === FlatShading ) ? true : false; + continue; + + } + + var currentValue = this[ key ]; + + if ( currentValue === undefined ) { + + console.warn( "THREE." + this.type + ": '" + key + "' is not a property of this material." ); + continue; + + } + + if ( currentValue && currentValue.isColor ) { + + currentValue.set( newValue ); + + } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) { + + currentValue.copy( newValue ); + + } else if ( key === 'overdraw' ) { + + // ensure overdraw is backwards-compatible with legacy boolean type + this[ key ] = Number( newValue ); + + } else { + + this[ key ] = newValue; + + } + + } + + }, + + toJSON: function ( meta ) { + + var isRoot = ( meta === undefined || typeof meta === 'string' ); + + if ( isRoot ) { + + meta = { + textures: {}, + images: {} + }; + + } + + var data = { + metadata: { + version: 4.5, + type: 'Material', + generator: 'Material.toJSON' + } + }; + + // standard Material serialization + data.uuid = this.uuid; + data.type = this.type; + + if ( this.name !== '' ) data.name = this.name; + + if ( this.color && this.color.isColor ) data.color = this.color.getHex(); + + if ( this.roughness !== undefined ) data.roughness = this.roughness; + if ( this.metalness !== undefined ) data.metalness = this.metalness; + + if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex(); + if ( this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity; + + if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex(); + if ( this.shininess !== undefined ) data.shininess = this.shininess; + if ( this.clearCoat !== undefined ) data.clearCoat = this.clearCoat; + if ( this.clearCoatRoughness !== undefined ) data.clearCoatRoughness = this.clearCoatRoughness; + + if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid; + if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid; + if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid; + if ( this.bumpMap && this.bumpMap.isTexture ) { + + data.bumpMap = this.bumpMap.toJSON( meta ).uuid; + data.bumpScale = this.bumpScale; + + } + if ( this.normalMap && this.normalMap.isTexture ) { + + data.normalMap = this.normalMap.toJSON( meta ).uuid; + data.normalScale = this.normalScale.toArray(); + + } + if ( this.displacementMap && this.displacementMap.isTexture ) { + + data.displacementMap = this.displacementMap.toJSON( meta ).uuid; + data.displacementScale = this.displacementScale; + data.displacementBias = this.displacementBias; + + } + if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid; + if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid; + + if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid; + if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid; + + if ( this.envMap && this.envMap.isTexture ) { + + data.envMap = this.envMap.toJSON( meta ).uuid; + data.reflectivity = this.reflectivity; // Scale behind envMap + + } + + if ( this.gradientMap && this.gradientMap.isTexture ) { + + data.gradientMap = this.gradientMap.toJSON( meta ).uuid; + + } + + if ( this.size !== undefined ) data.size = this.size; + if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation; + + if ( this.blending !== NormalBlending ) data.blending = this.blending; + if ( this.flatShading === true ) data.flatShading = this.flatShading; + if ( this.side !== FrontSide ) data.side = this.side; + if ( this.vertexColors !== NoColors ) data.vertexColors = this.vertexColors; + + if ( this.opacity < 1 ) data.opacity = this.opacity; + if ( this.transparent === true ) data.transparent = this.transparent; + + data.depthFunc = this.depthFunc; + data.depthTest = this.depthTest; + data.depthWrite = this.depthWrite; + + // rotation (SpriteMaterial) + if ( this.rotation !== 0 ) data.rotation = this.rotation; + + if ( this.linewidth !== 1 ) data.linewidth = this.linewidth; + if ( this.dashSize !== undefined ) data.dashSize = this.dashSize; + if ( this.gapSize !== undefined ) data.gapSize = this.gapSize; + if ( this.scale !== undefined ) data.scale = this.scale; + + if ( this.dithering === true ) data.dithering = true; + + if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest; + if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha; + + if ( this.wireframe === true ) data.wireframe = this.wireframe; + if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth; + if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap; + if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin; + + if ( this.morphTargets === true ) data.morphTargets = true; + if ( this.skinning === true ) data.skinning = true; + + if ( this.visible === false ) data.visible = false; + if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData; + + // TODO: Copied from Object3D.toJSON + + function extractFromCache( cache ) { + + var values = []; + + for ( var key in cache ) { + + var data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + + return values; + + } + + if ( isRoot ) { + + var textures = extractFromCache( meta.textures ); + var images = extractFromCache( meta.images ); + + if ( textures.length > 0 ) data.textures = textures; + if ( images.length > 0 ) data.images = images; + + } + + return data; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.name = source.name; + + this.fog = source.fog; + this.lights = source.lights; + + this.blending = source.blending; + this.side = source.side; + this.flatShading = source.flatShading; + this.vertexColors = source.vertexColors; + + this.opacity = source.opacity; + this.transparent = source.transparent; + + this.blendSrc = source.blendSrc; + this.blendDst = source.blendDst; + this.blendEquation = source.blendEquation; + this.blendSrcAlpha = source.blendSrcAlpha; + this.blendDstAlpha = source.blendDstAlpha; + this.blendEquationAlpha = source.blendEquationAlpha; + + this.depthFunc = source.depthFunc; + this.depthTest = source.depthTest; + this.depthWrite = source.depthWrite; + + this.colorWrite = source.colorWrite; + + this.precision = source.precision; + + this.polygonOffset = source.polygonOffset; + this.polygonOffsetFactor = source.polygonOffsetFactor; + this.polygonOffsetUnits = source.polygonOffsetUnits; + + this.dithering = source.dithering; + + this.alphaTest = source.alphaTest; + this.premultipliedAlpha = source.premultipliedAlpha; + + this.overdraw = source.overdraw; + + this.visible = source.visible; + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + this.clipShadows = source.clipShadows; + this.clipIntersection = source.clipIntersection; + + var srcPlanes = source.clippingPlanes, + dstPlanes = null; + + if ( srcPlanes !== null ) { + + var n = srcPlanes.length; + dstPlanes = new Array( n ); + + for ( var i = 0; i !== n; ++ i ) + dstPlanes[ i ] = srcPlanes[ i ].clone(); + + } + + this.clippingPlanes = dstPlanes; + + this.shadowSide = source.shadowSide; + + return this; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author bhouston / https://clara.io + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * + * opacity: , + * + * map: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: , + * + * wireframe: , + * wireframeLinewidth: + * } + */ + + function MeshDepthMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshDepthMaterial'; + + this.depthPacking = BasicDepthPacking; + + this.skinning = false; + this.morphTargets = false; + + this.map = null; + + this.alphaMap = null; + + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.fog = false; + this.lights = false; + + this.setValues( parameters ); + + } + + MeshDepthMaterial.prototype = Object.create( Material.prototype ); + MeshDepthMaterial.prototype.constructor = MeshDepthMaterial; + + MeshDepthMaterial.prototype.isMeshDepthMaterial = true; + + MeshDepthMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.depthPacking = source.depthPacking; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + return this; + + }; + + /** + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * + * referencePosition: , + * nearDistance: , + * farDistance: , + * + * skinning: , + * morphTargets: , + * + * map: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: + * + * } + */ + + function MeshDistanceMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshDistanceMaterial'; + + this.referencePosition = new Vector3(); + this.nearDistance = 1; + this.farDistance = 1000; + + this.skinning = false; + this.morphTargets = false; + + this.map = null; + + this.alphaMap = null; + + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; + + this.fog = false; + this.lights = false; + + this.setValues( parameters ); + + } + + MeshDistanceMaterial.prototype = Object.create( Material.prototype ); + MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial; + + MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true; + + MeshDistanceMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.referencePosition.copy( source.referencePosition ); + this.nearDistance = source.nearDistance; + this.farDistance = source.farDistance; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + + this.map = source.map; + + this.alphaMap = source.alphaMap; + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + return this; + + }; + + /** + * @author bhouston / http://clara.io + * @author WestLangley / http://github.com/WestLangley + */ + + function Box3( min, max ) { + + this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity ); + this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity ); + + } + + Object.assign( Box3.prototype, { + + isBox3: true, + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + setFromArray: function ( array ) { + + var minX = + Infinity; + var minY = + Infinity; + var minZ = + Infinity; + + var maxX = - Infinity; + var maxY = - Infinity; + var maxZ = - Infinity; + + for ( var i = 0, l = array.length; i < l; i += 3 ) { + + var x = array[ i ]; + var y = array[ i + 1 ]; + var z = array[ i + 2 ]; + + if ( x < minX ) minX = x; + if ( y < minY ) minY = y; + if ( z < minZ ) minZ = z; + + if ( x > maxX ) maxX = x; + if ( y > maxY ) maxY = y; + if ( z > maxZ ) maxZ = z; + + } + + this.min.set( minX, minY, minZ ); + this.max.set( maxX, maxY, maxZ ); + + return this; + + }, + + setFromBufferAttribute: function ( attribute ) { + + var minX = + Infinity; + var minY = + Infinity; + var minZ = + Infinity; + + var maxX = - Infinity; + var maxY = - Infinity; + var maxZ = - Infinity; + + for ( var i = 0, l = attribute.count; i < l; i ++ ) { + + var x = attribute.getX( i ); + var y = attribute.getY( i ); + var z = attribute.getZ( i ); + + if ( x < minX ) minX = x; + if ( y < minY ) minY = y; + if ( z < minZ ) minZ = z; + + if ( x > maxX ) maxX = x; + if ( y > maxY ) maxY = y; + if ( z > maxZ ) maxZ = z; + + } + + this.min.set( minX, minY, minZ ); + this.max.set( maxX, maxY, maxZ ); + + return this; + + }, + + setFromPoints: function ( points ) { + + this.makeEmpty(); + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ); + + } + + return this; + + }, + + setFromCenterAndSize: function () { + + var v1 = new Vector3(); + + return function setFromCenterAndSize( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + setFromObject: function ( object ) { + + this.makeEmpty(); + + return this.expandByObject( object ); + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = this.min.z = + Infinity; + this.max.x = this.max.y = this.max.z = - Infinity; + + return this; + + }, + + isEmpty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z ); + + }, + + getCenter: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return this.isEmpty() ? result.set( 0, 0, 0 ) : result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + getSize: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return this.isEmpty() ? result.set( 0, 0, 0 ) : result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + + }, + + expandByObject: function () { + + // Computes the world-axis-aligned bounding box of an object (including its children), + // accounting for both the object's, and children's, world transforms + + var scope, i, l; + + var v1 = new Vector3(); + + function traverse( node ) { + + var geometry = node.geometry; + + if ( geometry !== undefined ) { + + if ( geometry.isGeometry ) { + + var vertices = geometry.vertices; + + for ( i = 0, l = vertices.length; i < l; i ++ ) { + + v1.copy( vertices[ i ] ); + v1.applyMatrix4( node.matrixWorld ); + + scope.expandByPoint( v1 ); + + } + + } else if ( geometry.isBufferGeometry ) { + + var attribute = geometry.attributes.position; + + if ( attribute !== undefined ) { + + for ( i = 0, l = attribute.count; i < l; i ++ ) { + + v1.fromBufferAttribute( attribute, i ).applyMatrix4( node.matrixWorld ); + + scope.expandByPoint( v1 ); + + } + + } + + } + + } + + } + + return function expandByObject( object ) { + + scope = this; + + object.updateMatrixWorld( true ); + + object.traverse( traverse ); + + return this; + + }; + + }(), + + containsPoint: function ( point ) { + + return point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y || + point.z < this.min.z || point.z > this.max.z ? false : true; + + }, + + containsBox: function ( box ) { + + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y && + this.min.z <= box.min.z && box.max.z <= this.max.z; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new Vector3(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ), + ( point.z - this.min.z ) / ( this.max.z - this.min.z ) + ); + + }, + + intersectsBox: function ( box ) { + + // using 6 splitting planes to rule out intersections. + return box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y || + box.max.z < this.min.z || box.min.z > this.max.z ? false : true; + + }, + + intersectsSphere: ( function () { + + var closestPoint = new Vector3(); + + return function intersectsSphere( sphere ) { + + // Find the point on the AABB closest to the sphere center. + this.clampPoint( sphere.center, closestPoint ); + + // If that point is inside the sphere, the AABB and sphere intersect. + return closestPoint.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius ); + + }; + + } )(), + + intersectsPlane: function ( plane ) { + + // We compute the minimum and maximum dot product values. If those values + // are on the same side (back or front) of the plane, then there is no intersection. + + var min, max; + + if ( plane.normal.x > 0 ) { + + min = plane.normal.x * this.min.x; + max = plane.normal.x * this.max.x; + + } else { + + min = plane.normal.x * this.max.x; + max = plane.normal.x * this.min.x; + + } + + if ( plane.normal.y > 0 ) { + + min += plane.normal.y * this.min.y; + max += plane.normal.y * this.max.y; + + } else { + + min += plane.normal.y * this.max.y; + max += plane.normal.y * this.min.y; + + } + + if ( plane.normal.z > 0 ) { + + min += plane.normal.z * this.min.z; + max += plane.normal.z * this.max.z; + + } else { + + min += plane.normal.z * this.max.z; + max += plane.normal.z * this.min.z; + + } + + return ( min <= plane.constant && max >= plane.constant ); + + }, + + intersectsTriangle: ( function () { + + // triangle centered vertices + var v0 = new Vector3(); + var v1 = new Vector3(); + var v2 = new Vector3(); + + // triangle edge vectors + var f0 = new Vector3(); + var f1 = new Vector3(); + var f2 = new Vector3(); + + var testAxis = new Vector3(); + + var center = new Vector3(); + var extents = new Vector3(); + + var triangleNormal = new Vector3(); + + function satForAxes( axes ) { + + var i, j; + + for ( i = 0, j = axes.length - 3; i <= j; i += 3 ) { + + testAxis.fromArray( axes, i ); + // project the aabb onto the seperating axis + var r = extents.x * Math.abs( testAxis.x ) + extents.y * Math.abs( testAxis.y ) + extents.z * Math.abs( testAxis.z ); + // project all 3 vertices of the triangle onto the seperating axis + var p0 = v0.dot( testAxis ); + var p1 = v1.dot( testAxis ); + var p2 = v2.dot( testAxis ); + // actual test, basically see if either of the most extreme of the triangle points intersects r + if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) { + + // points of the projected triangle are outside the projected half-length of the aabb + // the axis is seperating and we can exit + return false; + + } + + } + + return true; + + } + + return function intersectsTriangle( triangle ) { + + if ( this.isEmpty() ) { + + return false; + + } + + // compute box center and extents + this.getCenter( center ); + extents.subVectors( this.max, center ); + + // translate triangle to aabb origin + v0.subVectors( triangle.a, center ); + v1.subVectors( triangle.b, center ); + v2.subVectors( triangle.c, center ); + + // compute edge vectors for triangle + f0.subVectors( v1, v0 ); + f1.subVectors( v2, v1 ); + f2.subVectors( v0, v2 ); + + // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb + // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation + // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned) + var axes = [ + 0, - f0.z, f0.y, 0, - f1.z, f1.y, 0, - f2.z, f2.y, + f0.z, 0, - f0.x, f1.z, 0, - f1.x, f2.z, 0, - f2.x, + - f0.y, f0.x, 0, - f1.y, f1.x, 0, - f2.y, f2.x, 0 + ]; + if ( ! satForAxes( axes ) ) { + + return false; + + } + + // test 3 face normals from the aabb + axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ]; + if ( ! satForAxes( axes ) ) { + + return false; + + } + + // finally testing the face normal of the triangle + // use already existing triangle edge vectors here + triangleNormal.crossVectors( f0, f1 ); + axes = [ triangleNormal.x, triangleNormal.y, triangleNormal.z ]; + return satForAxes( axes ); + + }; + + } )(), + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function () { + + var v1 = new Vector3(); + + return function distanceToPoint( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + getBoundingSphere: function () { + + var v1 = new Vector3(); + + return function getBoundingSphere( optionalTarget ) { + + var result = optionalTarget || new Sphere(); + + this.getCenter( result.center ); + + result.radius = this.getSize( v1 ).length() * 0.5; + + return result; + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values. + if ( this.isEmpty() ) this.makeEmpty(); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + applyMatrix4: function () { + + var points = [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ]; + + return function applyMatrix4( matrix ) { + + // transform of empty box is an empty box. + if ( this.isEmpty() ) return this; + + // NOTE: I am using a binary pattern to specify all 2^3 combinations below + points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000 + points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001 + points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010 + points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011 + points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100 + points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101 + points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110 + points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111 + + this.setFromPoints( points ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + } + + } ); + + /** + * @author bhouston / http://clara.io + * @author mrdoob / http://mrdoob.com/ + */ + + function Sphere( center, radius ) { + + this.center = ( center !== undefined ) ? center : new Vector3(); + this.radius = ( radius !== undefined ) ? radius : 0; + + } + + Object.assign( Sphere.prototype, { + + set: function ( center, radius ) { + + this.center.copy( center ); + this.radius = radius; + + return this; + + }, + + setFromPoints: function () { + + var box = new Box3(); + + return function setFromPoints( points, optionalCenter ) { + + var center = this.center; + + if ( optionalCenter !== undefined ) { + + center.copy( optionalCenter ); + + } else { + + box.setFromPoints( points ).getCenter( center ); + + } + + var maxRadiusSq = 0; + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) ); + + } + + this.radius = Math.sqrt( maxRadiusSq ); + + return this; + + }; + + }(), + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( sphere ) { + + this.center.copy( sphere.center ); + this.radius = sphere.radius; + + return this; + + }, + + empty: function () { + + return ( this.radius <= 0 ); + + }, + + containsPoint: function ( point ) { + + return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) ); + + }, + + distanceToPoint: function ( point ) { + + return ( point.distanceTo( this.center ) - this.radius ); + + }, + + intersectsSphere: function ( sphere ) { + + var radiusSum = this.radius + sphere.radius; + + return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum ); + + }, + + intersectsBox: function ( box ) { + + return box.intersectsSphere( this ); + + }, + + intersectsPlane: function ( plane ) { + + return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var deltaLengthSq = this.center.distanceToSquared( point ); + + var result = optionalTarget || new Vector3(); + + result.copy( point ); + + if ( deltaLengthSq > ( this.radius * this.radius ) ) { + + result.sub( this.center ).normalize(); + result.multiplyScalar( this.radius ).add( this.center ); + + } + + return result; + + }, + + getBoundingBox: function ( optionalTarget ) { + + var box = optionalTarget || new Box3(); + + box.set( this.center, this.center ); + box.expandByScalar( this.radius ); + + return box; + + }, + + applyMatrix4: function ( matrix ) { + + this.center.applyMatrix4( matrix ); + this.radius = this.radius * matrix.getMaxScaleOnAxis(); + + return this; + + }, + + translate: function ( offset ) { + + this.center.add( offset ); + + return this; + + }, + + equals: function ( sphere ) { + + return sphere.center.equals( this.center ) && ( sphere.radius === this.radius ); + + } + + } ); + + /** + * @author bhouston / http://clara.io + */ + + function Plane( normal, constant ) { + + // normal is assumed to be normalized + + this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 ); + this.constant = ( constant !== undefined ) ? constant : 0; + + } + + Object.assign( Plane.prototype, { + + set: function ( normal, constant ) { + + this.normal.copy( normal ); + this.constant = constant; + + return this; + + }, + + setComponents: function ( x, y, z, w ) { + + this.normal.set( x, y, z ); + this.constant = w; + + return this; + + }, + + setFromNormalAndCoplanarPoint: function ( normal, point ) { + + this.normal.copy( normal ); + this.constant = - point.dot( this.normal ); + + return this; + + }, + + setFromCoplanarPoints: function () { + + var v1 = new Vector3(); + var v2 = new Vector3(); + + return function setFromCoplanarPoints( a, b, c ) { + + var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize(); + + // Q: should an error be thrown if normal is zero (e.g. degenerate plane)? + + this.setFromNormalAndCoplanarPoint( normal, a ); + + return this; + + }; + + }(), + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( plane ) { + + this.normal.copy( plane.normal ); + this.constant = plane.constant; + + return this; + + }, + + normalize: function () { + + // Note: will lead to a divide by zero if the plane is invalid. + + var inverseNormalLength = 1.0 / this.normal.length(); + this.normal.multiplyScalar( inverseNormalLength ); + this.constant *= inverseNormalLength; + + return this; + + }, + + negate: function () { + + this.constant *= - 1; + this.normal.negate(); + + return this; + + }, + + distanceToPoint: function ( point ) { + + return this.normal.dot( point ) + this.constant; + + }, + + distanceToSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) - sphere.radius; + + }, + + projectPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + return result.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point ); + + }, + + intersectLine: function () { + + var v1 = new Vector3(); + + return function intersectLine( line, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + var direction = line.delta( v1 ); + + var denominator = this.normal.dot( direction ); + + if ( denominator === 0 ) { + + // line is coplanar, return origin + if ( this.distanceToPoint( line.start ) === 0 ) { + + return result.copy( line.start ); + + } + + // Unsure if this is the correct method to handle this case. + return undefined; + + } + + var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator; + + if ( t < 0 || t > 1 ) { + + return undefined; + + } + + return result.copy( direction ).multiplyScalar( t ).add( line.start ); + + }; + + }(), + + intersectsLine: function ( line ) { + + // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it. + + var startSign = this.distanceToPoint( line.start ); + var endSign = this.distanceToPoint( line.end ); + + return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 ); + + }, + + intersectsBox: function ( box ) { + + return box.intersectsPlane( this ); + + }, + + intersectsSphere: function ( sphere ) { + + return sphere.intersectsPlane( this ); + + }, + + coplanarPoint: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + return result.copy( this.normal ).multiplyScalar( - this.constant ); + + }, + + applyMatrix4: function () { + + var v1 = new Vector3(); + var m1 = new Matrix3(); + + return function applyMatrix4( matrix, optionalNormalMatrix ) { + + var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix ); + + var referencePoint = this.coplanarPoint( v1 ).applyMatrix4( matrix ); + + var normal = this.normal.applyMatrix3( normalMatrix ).normalize(); + + this.constant = - referencePoint.dot( normal ); + + return this; + + }; + + }(), + + translate: function ( offset ) { + + this.constant -= offset.dot( this.normal ); + + return this; + + }, + + equals: function ( plane ) { + + return plane.normal.equals( this.normal ) && ( plane.constant === this.constant ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author bhouston / http://clara.io + */ + + function Frustum( p0, p1, p2, p3, p4, p5 ) { + + this.planes = [ + + ( p0 !== undefined ) ? p0 : new Plane(), + ( p1 !== undefined ) ? p1 : new Plane(), + ( p2 !== undefined ) ? p2 : new Plane(), + ( p3 !== undefined ) ? p3 : new Plane(), + ( p4 !== undefined ) ? p4 : new Plane(), + ( p5 !== undefined ) ? p5 : new Plane() + + ]; + + } + + Object.assign( Frustum.prototype, { + + set: function ( p0, p1, p2, p3, p4, p5 ) { + + var planes = this.planes; + + planes[ 0 ].copy( p0 ); + planes[ 1 ].copy( p1 ); + planes[ 2 ].copy( p2 ); + planes[ 3 ].copy( p3 ); + planes[ 4 ].copy( p4 ); + planes[ 5 ].copy( p5 ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( frustum ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + planes[ i ].copy( frustum.planes[ i ] ); + + } + + return this; + + }, + + setFromMatrix: function ( m ) { + + var planes = this.planes; + var me = m.elements; + var me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ]; + var me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ]; + var me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ]; + var me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ]; + + planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize(); + planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize(); + planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize(); + planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize(); + planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize(); + planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize(); + + return this; + + }, + + intersectsObject: function () { + + var sphere = new Sphere(); + + return function intersectsObject( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) + geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ) + .applyMatrix4( object.matrixWorld ); + + return this.intersectsSphere( sphere ); + + }; + + }(), + + intersectsSprite: function () { + + var sphere = new Sphere(); + + return function intersectsSprite( sprite ) { + + sphere.center.set( 0, 0, 0 ); + sphere.radius = 0.7071067811865476; + sphere.applyMatrix4( sprite.matrixWorld ); + + return this.intersectsSphere( sphere ); + + }; + + }(), + + intersectsSphere: function ( sphere ) { + + var planes = this.planes; + var center = sphere.center; + var negRadius = - sphere.radius; + + for ( var i = 0; i < 6; i ++ ) { + + var distance = planes[ i ].distanceToPoint( center ); + + if ( distance < negRadius ) { + + return false; + + } + + } + + return true; + + }, + + intersectsBox: function () { + + var p1 = new Vector3(), + p2 = new Vector3(); + + return function intersectsBox( box ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + var plane = planes[ i ]; + + p1.x = plane.normal.x > 0 ? box.min.x : box.max.x; + p2.x = plane.normal.x > 0 ? box.max.x : box.min.x; + p1.y = plane.normal.y > 0 ? box.min.y : box.max.y; + p2.y = plane.normal.y > 0 ? box.max.y : box.min.y; + p1.z = plane.normal.z > 0 ? box.min.z : box.max.z; + p2.z = plane.normal.z > 0 ? box.max.z : box.min.z; + + var d1 = plane.distanceToPoint( p1 ); + var d2 = plane.distanceToPoint( p2 ); + + // if both outside plane, no intersection + + if ( d1 < 0 && d2 < 0 ) { + + return false; + + } + + } + + return true; + + }; + + }(), + + containsPoint: function ( point ) { + + var planes = this.planes; + + for ( var i = 0; i < 6; i ++ ) { + + if ( planes[ i ].distanceToPoint( point ) < 0 ) { + + return false; + + } + + } + + return true; + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLShadowMap( _renderer, _objects, maxTextureSize ) { + + var _frustum = new Frustum(), + _projScreenMatrix = new Matrix4(), + + _shadowMapSize = new Vector2(), + _maxShadowMapSize = new Vector2( maxTextureSize, maxTextureSize ), + + _lookTarget = new Vector3(), + _lightPositionWorld = new Vector3(), + + _MorphingFlag = 1, + _SkinningFlag = 2, + + _NumberOfMaterialVariants = ( _MorphingFlag | _SkinningFlag ) + 1, + + _depthMaterials = new Array( _NumberOfMaterialVariants ), + _distanceMaterials = new Array( _NumberOfMaterialVariants ), + + _materialCache = {}; + + var shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide }; + + var cubeDirections = [ + new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ), + new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 ) + ]; + + var cubeUps = [ + new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), + new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 ) + ]; + + var cube2DViewPorts = [ + new Vector4(), new Vector4(), new Vector4(), + new Vector4(), new Vector4(), new Vector4() + ]; + + // init + + for ( var i = 0; i !== _NumberOfMaterialVariants; ++ i ) { + + var useMorphing = ( i & _MorphingFlag ) !== 0; + var useSkinning = ( i & _SkinningFlag ) !== 0; + + var depthMaterial = new MeshDepthMaterial( { + + depthPacking: RGBADepthPacking, + + morphTargets: useMorphing, + skinning: useSkinning + + } ); + + _depthMaterials[ i ] = depthMaterial; + + // + + var distanceMaterial = new MeshDistanceMaterial( { + + morphTargets: useMorphing, + skinning: useSkinning + + } ); + + _distanceMaterials[ i ] = distanceMaterial; + + } + + // + + var scope = this; + + this.enabled = false; + + this.autoUpdate = true; + this.needsUpdate = false; + + this.type = PCFShadowMap; + + this.render = function ( lights, scene, camera ) { + + if ( scope.enabled === false ) return; + if ( scope.autoUpdate === false && scope.needsUpdate === false ) return; + + if ( lights.length === 0 ) return; + + // TODO Clean up (needed in case of contextlost) + var _gl = _renderer.context; + var _state = _renderer.state; + + // Set GL state for depth map. + _state.disable( _gl.BLEND ); + _state.buffers.color.setClear( 1, 1, 1, 1 ); + _state.buffers.depth.setTest( true ); + _state.setScissorTest( false ); + + // render depth map + + var faceCount; + + for ( var i = 0, il = lights.length; i < il; i ++ ) { + + var light = lights[ i ]; + var shadow = light.shadow; + var isPointLight = light && light.isPointLight; + + if ( shadow === undefined ) { + + console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' ); + continue; + + } + + var shadowCamera = shadow.camera; + + _shadowMapSize.copy( shadow.mapSize ); + _shadowMapSize.min( _maxShadowMapSize ); + + if ( isPointLight ) { + + var vpWidth = _shadowMapSize.x; + var vpHeight = _shadowMapSize.y; + + // These viewports map a cube-map onto a 2D texture with the + // following orientation: + // + // xzXZ + // y Y + // + // X - Positive x direction + // x - Negative x direction + // Y - Positive y direction + // y - Negative y direction + // Z - Positive z direction + // z - Negative z direction + + // positive X + cube2DViewPorts[ 0 ].set( vpWidth * 2, vpHeight, vpWidth, vpHeight ); + // negative X + cube2DViewPorts[ 1 ].set( 0, vpHeight, vpWidth, vpHeight ); + // positive Z + cube2DViewPorts[ 2 ].set( vpWidth * 3, vpHeight, vpWidth, vpHeight ); + // negative Z + cube2DViewPorts[ 3 ].set( vpWidth, vpHeight, vpWidth, vpHeight ); + // positive Y + cube2DViewPorts[ 4 ].set( vpWidth * 3, 0, vpWidth, vpHeight ); + // negative Y + cube2DViewPorts[ 5 ].set( vpWidth, 0, vpWidth, vpHeight ); + + _shadowMapSize.x *= 4.0; + _shadowMapSize.y *= 2.0; + + } + + if ( shadow.map === null ) { + + var pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat }; + + shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars ); + shadow.map.texture.name = light.name + ".shadowMap"; + + shadowCamera.updateProjectionMatrix(); + + } + + if ( shadow.isSpotLightShadow ) { + + shadow.update( light ); + + } + + var shadowMap = shadow.map; + var shadowMatrix = shadow.matrix; + + _lightPositionWorld.setFromMatrixPosition( light.matrixWorld ); + shadowCamera.position.copy( _lightPositionWorld ); + + if ( isPointLight ) { + + faceCount = 6; + + // for point lights we set the shadow matrix to be a translation-only matrix + // equal to inverse of the light's position + + shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z ); + + } else { + + faceCount = 1; + + _lookTarget.setFromMatrixPosition( light.target.matrixWorld ); + shadowCamera.lookAt( _lookTarget ); + shadowCamera.updateMatrixWorld(); + + // compute shadow matrix + + shadowMatrix.set( + 0.5, 0.0, 0.0, 0.5, + 0.0, 0.5, 0.0, 0.5, + 0.0, 0.0, 0.5, 0.5, + 0.0, 0.0, 0.0, 1.0 + ); + + shadowMatrix.multiply( shadowCamera.projectionMatrix ); + shadowMatrix.multiply( shadowCamera.matrixWorldInverse ); + + } + + _renderer.setRenderTarget( shadowMap ); + _renderer.clear(); + + // render shadow map for each cube face (if omni-directional) or + // run a single pass if not + + for ( var face = 0; face < faceCount; face ++ ) { + + if ( isPointLight ) { + + _lookTarget.copy( shadowCamera.position ); + _lookTarget.add( cubeDirections[ face ] ); + shadowCamera.up.copy( cubeUps[ face ] ); + shadowCamera.lookAt( _lookTarget ); + shadowCamera.updateMatrixWorld(); + + var vpDimensions = cube2DViewPorts[ face ]; + _state.viewport( vpDimensions ); + + } + + // update camera matrices and frustum + + _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + // set object matrices & frustum culling + + renderObject( scene, camera, shadowCamera, isPointLight ); + + } + + } + + scope.needsUpdate = false; + + }; + + function getDepthMaterial( object, material, isPointLight, lightPositionWorld, shadowCameraNear, shadowCameraFar ) { + + var geometry = object.geometry; + + var result = null; + + var materialVariants = _depthMaterials; + var customMaterial = object.customDepthMaterial; + + if ( isPointLight ) { + + materialVariants = _distanceMaterials; + customMaterial = object.customDistanceMaterial; + + } + + if ( ! customMaterial ) { + + var useMorphing = false; + + if ( material.morphTargets ) { + + if ( geometry && geometry.isBufferGeometry ) { + + useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0; + + } else if ( geometry && geometry.isGeometry ) { + + useMorphing = geometry.morphTargets && geometry.morphTargets.length > 0; + + } + + } + + if ( object.isSkinnedMesh && material.skinning === false ) { + + console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object ); + + } + + var useSkinning = object.isSkinnedMesh && material.skinning; + + var variantIndex = 0; + + if ( useMorphing ) variantIndex |= _MorphingFlag; + if ( useSkinning ) variantIndex |= _SkinningFlag; + + result = materialVariants[ variantIndex ]; + + } else { + + result = customMaterial; + + } + + if ( _renderer.localClippingEnabled && + material.clipShadows === true && + material.clippingPlanes.length !== 0 ) { + + // in this case we need a unique material instance reflecting the + // appropriate state + + var keyA = result.uuid, keyB = material.uuid; + + var materialsForVariant = _materialCache[ keyA ]; + + if ( materialsForVariant === undefined ) { + + materialsForVariant = {}; + _materialCache[ keyA ] = materialsForVariant; + + } + + var cachedMaterial = materialsForVariant[ keyB ]; + + if ( cachedMaterial === undefined ) { + + cachedMaterial = result.clone(); + materialsForVariant[ keyB ] = cachedMaterial; + + } + + result = cachedMaterial; + + } + + result.visible = material.visible; + result.wireframe = material.wireframe; + + result.side = ( material.shadowSide != null ) ? material.shadowSide : shadowSide[ material.side ]; + + result.clipShadows = material.clipShadows; + result.clippingPlanes = material.clippingPlanes; + result.clipIntersection = material.clipIntersection; + + result.wireframeLinewidth = material.wireframeLinewidth; + result.linewidth = material.linewidth; + + if ( isPointLight && result.isMeshDistanceMaterial ) { + + result.referencePosition.copy( lightPositionWorld ); + result.nearDistance = shadowCameraNear; + result.farDistance = shadowCameraFar; + + } + + return result; + + } + + function renderObject( object, camera, shadowCamera, isPointLight ) { + + if ( object.visible === false ) return; + + var visible = object.layers.test( camera.layers ); + + if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) { + + if ( object.castShadow && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) { + + object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld ); + + var geometry = _objects.update( object ); + var material = object.material; + + if ( Array.isArray( material ) ) { + + var groups = geometry.groups; + + for ( var k = 0, kl = groups.length; k < kl; k ++ ) { + + var group = groups[ k ]; + var groupMaterial = material[ group.materialIndex ]; + + if ( groupMaterial && groupMaterial.visible ) { + + var depthMaterial = getDepthMaterial( object, groupMaterial, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far ); + _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group ); + + } + + } + + } else if ( material.visible ) { + + var depthMaterial = getDepthMaterial( object, material, isPointLight, _lightPositionWorld, shadowCamera.near, shadowCamera.far ); + _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null ); + + } + + } + + } + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + renderObject( children[ i ], camera, shadowCamera, isPointLight ); + + } + + } + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLAttributes( gl ) { + + var buffers = new WeakMap(); + + function createBuffer( attribute, bufferType ) { + + var array = attribute.array; + var usage = attribute.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW; + + var buffer = gl.createBuffer(); + + gl.bindBuffer( bufferType, buffer ); + gl.bufferData( bufferType, array, usage ); + + attribute.onUploadCallback(); + + var type = gl.FLOAT; + + if ( array instanceof Float32Array ) { + + type = gl.FLOAT; + + } else if ( array instanceof Float64Array ) { + + console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' ); + + } else if ( array instanceof Uint16Array ) { + + type = gl.UNSIGNED_SHORT; + + } else if ( array instanceof Int16Array ) { + + type = gl.SHORT; + + } else if ( array instanceof Uint32Array ) { + + type = gl.UNSIGNED_INT; + + } else if ( array instanceof Int32Array ) { + + type = gl.INT; + + } else if ( array instanceof Int8Array ) { + + type = gl.BYTE; + + } else if ( array instanceof Uint8Array ) { + + type = gl.UNSIGNED_BYTE; + + } + + return { + buffer: buffer, + type: type, + bytesPerElement: array.BYTES_PER_ELEMENT, + version: attribute.version + }; + + } + + function updateBuffer( buffer, attribute, bufferType ) { + + var array = attribute.array; + var updateRange = attribute.updateRange; + + gl.bindBuffer( bufferType, buffer ); + + if ( attribute.dynamic === false ) { + + gl.bufferData( bufferType, array, gl.STATIC_DRAW ); + + } else if ( updateRange.count === - 1 ) { + + // Not using update ranges + + gl.bufferSubData( bufferType, 0, array ); + + } else if ( updateRange.count === 0 ) { + + console.error( 'THREE.WebGLObjects.updateBuffer: dynamic THREE.BufferAttribute marked as needsUpdate but updateRange.count is 0, ensure you are using set methods or updating manually.' ); + + } else { + + gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT, + array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) ); + + updateRange.count = - 1; // reset range + + } + + } + + // + + function get( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + return buffers.get( attribute ); + + } + + function remove( attribute ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + var data = buffers.get( attribute ); + + if ( data ) { + + gl.deleteBuffer( data.buffer ); + + buffers.delete( attribute ); + + } + + } + + function update( attribute, bufferType ) { + + if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data; + + var data = buffers.get( attribute ); + + if ( data === undefined ) { + + buffers.set( attribute, createBuffer( attribute, bufferType ) ); + + } else if ( data.version < attribute.version ) { + + updateBuffer( data.buffer, attribute, bufferType ); + + data.version = attribute.version; + + } + + } + + return { + + get: get, + remove: remove, + update: update + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + * @author bhouston / http://clara.io + */ + + function Euler( x, y, z, order ) { + + this._x = x || 0; + this._y = y || 0; + this._z = z || 0; + this._order = order || Euler.DefaultOrder; + + } + + Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ]; + + Euler.DefaultOrder = 'XYZ'; + + Object.defineProperties( Euler.prototype, { + + x: { + + get: function () { + + return this._x; + + }, + + set: function ( value ) { + + this._x = value; + this.onChangeCallback(); + + } + + }, + + y: { + + get: function () { + + return this._y; + + }, + + set: function ( value ) { + + this._y = value; + this.onChangeCallback(); + + } + + }, + + z: { + + get: function () { + + return this._z; + + }, + + set: function ( value ) { + + this._z = value; + this.onChangeCallback(); + + } + + }, + + order: { + + get: function () { + + return this._order; + + }, + + set: function ( value ) { + + this._order = value; + this.onChangeCallback(); + + } + + } + + } ); + + Object.assign( Euler.prototype, { + + isEuler: true, + + set: function ( x, y, z, order ) { + + this._x = x; + this._y = y; + this._z = z; + this._order = order || this._order; + + this.onChangeCallback(); + + return this; + + }, + + clone: function () { + + return new this.constructor( this._x, this._y, this._z, this._order ); + + }, + + copy: function ( euler ) { + + this._x = euler._x; + this._y = euler._y; + this._z = euler._z; + this._order = euler._order; + + this.onChangeCallback(); + + return this; + + }, + + setFromRotationMatrix: function ( m, order, update ) { + + var clamp = _Math.clamp; + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + var te = m.elements; + var m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ]; + var m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ]; + var m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ]; + + order = order || this._order; + + if ( order === 'XYZ' ) { + + this._y = Math.asin( clamp( m13, - 1, 1 ) ); + + if ( Math.abs( m13 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m33 ); + this._z = Math.atan2( - m12, m11 ); + + } else { + + this._x = Math.atan2( m32, m22 ); + this._z = 0; + + } + + } else if ( order === 'YXZ' ) { + + this._x = Math.asin( - clamp( m23, - 1, 1 ) ); + + if ( Math.abs( m23 ) < 0.99999 ) { + + this._y = Math.atan2( m13, m33 ); + this._z = Math.atan2( m21, m22 ); + + } else { + + this._y = Math.atan2( - m31, m11 ); + this._z = 0; + + } + + } else if ( order === 'ZXY' ) { + + this._x = Math.asin( clamp( m32, - 1, 1 ) ); + + if ( Math.abs( m32 ) < 0.99999 ) { + + this._y = Math.atan2( - m31, m33 ); + this._z = Math.atan2( - m12, m22 ); + + } else { + + this._y = 0; + this._z = Math.atan2( m21, m11 ); + + } + + } else if ( order === 'ZYX' ) { + + this._y = Math.asin( - clamp( m31, - 1, 1 ) ); + + if ( Math.abs( m31 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m33 ); + this._z = Math.atan2( m21, m11 ); + + } else { + + this._x = 0; + this._z = Math.atan2( - m12, m22 ); + + } + + } else if ( order === 'YZX' ) { + + this._z = Math.asin( clamp( m21, - 1, 1 ) ); + + if ( Math.abs( m21 ) < 0.99999 ) { + + this._x = Math.atan2( - m23, m22 ); + this._y = Math.atan2( - m31, m11 ); + + } else { + + this._x = 0; + this._y = Math.atan2( m13, m33 ); + + } + + } else if ( order === 'XZY' ) { + + this._z = Math.asin( - clamp( m12, - 1, 1 ) ); + + if ( Math.abs( m12 ) < 0.99999 ) { + + this._x = Math.atan2( m32, m22 ); + this._y = Math.atan2( m13, m11 ); + + } else { + + this._x = Math.atan2( - m23, m33 ); + this._y = 0; + + } + + } else { + + console.warn( 'THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order ); + + } + + this._order = order; + + if ( update !== false ) this.onChangeCallback(); + + return this; + + }, + + setFromQuaternion: function () { + + var matrix = new Matrix4(); + + return function setFromQuaternion( q, order, update ) { + + matrix.makeRotationFromQuaternion( q ); + + return this.setFromRotationMatrix( matrix, order, update ); + + }; + + }(), + + setFromVector3: function ( v, order ) { + + return this.set( v.x, v.y, v.z, order || this._order ); + + }, + + reorder: function () { + + // WARNING: this discards revolution information -bhouston + + var q = new Quaternion(); + + return function reorder( newOrder ) { + + q.setFromEuler( this ); + + return this.setFromQuaternion( q, newOrder ); + + }; + + }(), + + equals: function ( euler ) { + + return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order ); + + }, + + fromArray: function ( array ) { + + this._x = array[ 0 ]; + this._y = array[ 1 ]; + this._z = array[ 2 ]; + if ( array[ 3 ] !== undefined ) this._order = array[ 3 ]; + + this.onChangeCallback(); + + return this; + + }, + + toArray: function ( array, offset ) { + + if ( array === undefined ) array = []; + if ( offset === undefined ) offset = 0; + + array[ offset ] = this._x; + array[ offset + 1 ] = this._y; + array[ offset + 2 ] = this._z; + array[ offset + 3 ] = this._order; + + return array; + + }, + + toVector3: function ( optionalResult ) { + + if ( optionalResult ) { + + return optionalResult.set( this._x, this._y, this._z ); + + } else { + + return new Vector3( this._x, this._y, this._z ); + + } + + }, + + onChange: function ( callback ) { + + this.onChangeCallback = callback; + + return this; + + }, + + onChangeCallback: function () {} + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Layers() { + + this.mask = 1 | 0; + + } + + Object.assign( Layers.prototype, { + + set: function ( channel ) { + + this.mask = 1 << channel | 0; + + }, + + enable: function ( channel ) { + + this.mask |= 1 << channel | 0; + + }, + + toggle: function ( channel ) { + + this.mask ^= 1 << channel | 0; + + }, + + disable: function ( channel ) { + + this.mask &= ~ ( 1 << channel | 0 ); + + }, + + test: function ( layers ) { + + return ( this.mask & layers.mask ) !== 0; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author WestLangley / http://github.com/WestLangley + * @author elephantatwork / www.elephantatwork.ch + */ + + var object3DId = 0; + + function Object3D() { + + Object.defineProperty( this, 'id', { value: object3DId ++ } ); + + this.uuid = _Math.generateUUID(); + + this.name = ''; + this.type = 'Object3D'; + + this.parent = null; + this.children = []; + + this.up = Object3D.DefaultUp.clone(); + + var position = new Vector3(); + var rotation = new Euler(); + var quaternion = new Quaternion(); + var scale = new Vector3( 1, 1, 1 ); + + function onRotationChange() { + + quaternion.setFromEuler( rotation, false ); + + } + + function onQuaternionChange() { + + rotation.setFromQuaternion( quaternion, undefined, false ); + + } + + rotation.onChange( onRotationChange ); + quaternion.onChange( onQuaternionChange ); + + Object.defineProperties( this, { + position: { + enumerable: true, + value: position + }, + rotation: { + enumerable: true, + value: rotation + }, + quaternion: { + enumerable: true, + value: quaternion + }, + scale: { + enumerable: true, + value: scale + }, + modelViewMatrix: { + value: new Matrix4() + }, + normalMatrix: { + value: new Matrix3() + } + } ); + + this.matrix = new Matrix4(); + this.matrixWorld = new Matrix4(); + + this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate; + this.matrixWorldNeedsUpdate = false; + + this.layers = new Layers(); + this.visible = true; + + this.castShadow = false; + this.receiveShadow = false; + + this.frustumCulled = true; + this.renderOrder = 0; + + this.userData = {}; + + } + + Object3D.DefaultUp = new Vector3( 0, 1, 0 ); + Object3D.DefaultMatrixAutoUpdate = true; + + Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Object3D, + + isObject3D: true, + + onBeforeRender: function () {}, + onAfterRender: function () {}, + + applyMatrix: function ( matrix ) { + + this.matrix.multiplyMatrices( matrix, this.matrix ); + + this.matrix.decompose( this.position, this.quaternion, this.scale ); + + }, + + applyQuaternion: function ( q ) { + + this.quaternion.premultiply( q ); + + return this; + + }, + + setRotationFromAxisAngle: function ( axis, angle ) { + + // assumes axis is normalized + + this.quaternion.setFromAxisAngle( axis, angle ); + + }, + + setRotationFromEuler: function ( euler ) { + + this.quaternion.setFromEuler( euler, true ); + + }, + + setRotationFromMatrix: function ( m ) { + + // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled) + + this.quaternion.setFromRotationMatrix( m ); + + }, + + setRotationFromQuaternion: function ( q ) { + + // assumes q is normalized + + this.quaternion.copy( q ); + + }, + + rotateOnAxis: function () { + + // rotate object on axis in object space + // axis is assumed to be normalized + + var q1 = new Quaternion(); + + return function rotateOnAxis( axis, angle ) { + + q1.setFromAxisAngle( axis, angle ); + + this.quaternion.multiply( q1 ); + + return this; + + }; + + }(), + + rotateOnWorldAxis: function () { + + // rotate object on axis in world space + // axis is assumed to be normalized + // method assumes no rotated parent + + var q1 = new Quaternion(); + + return function rotateOnWorldAxis( axis, angle ) { + + q1.setFromAxisAngle( axis, angle ); + + this.quaternion.premultiply( q1 ); + + return this; + + }; + + }(), + + rotateX: function () { + + var v1 = new Vector3( 1, 0, 0 ); + + return function rotateX( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateY: function () { + + var v1 = new Vector3( 0, 1, 0 ); + + return function rotateY( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + rotateZ: function () { + + var v1 = new Vector3( 0, 0, 1 ); + + return function rotateZ( angle ) { + + return this.rotateOnAxis( v1, angle ); + + }; + + }(), + + translateOnAxis: function () { + + // translate object by distance along axis in object space + // axis is assumed to be normalized + + var v1 = new Vector3(); + + return function translateOnAxis( axis, distance ) { + + v1.copy( axis ).applyQuaternion( this.quaternion ); + + this.position.add( v1.multiplyScalar( distance ) ); + + return this; + + }; + + }(), + + translateX: function () { + + var v1 = new Vector3( 1, 0, 0 ); + + return function translateX( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateY: function () { + + var v1 = new Vector3( 0, 1, 0 ); + + return function translateY( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + translateZ: function () { + + var v1 = new Vector3( 0, 0, 1 ); + + return function translateZ( distance ) { + + return this.translateOnAxis( v1, distance ); + + }; + + }(), + + localToWorld: function ( vector ) { + + return vector.applyMatrix4( this.matrixWorld ); + + }, + + worldToLocal: function () { + + var m1 = new Matrix4(); + + return function worldToLocal( vector ) { + + return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) ); + + }; + + }(), + + lookAt: function () { + + // This method does not support objects with rotated and/or translated parent(s) + + var m1 = new Matrix4(); + var vector = new Vector3(); + + return function lookAt( x, y, z ) { + + if ( x.isVector3 ) { + + vector.copy( x ); + + } else { + + vector.set( x, y, z ); + + } + + if ( this.isCamera ) { + + m1.lookAt( this.position, vector, this.up ); + + } else { + + m1.lookAt( vector, this.position, this.up ); + + } + + this.quaternion.setFromRotationMatrix( m1 ); + + }; + + }(), + + add: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i ++ ) { + + this.add( arguments[ i ] ); + + } + + return this; + + } + + if ( object === this ) { + + console.error( "THREE.Object3D.add: object can't be added as a child of itself.", object ); + return this; + + } + + if ( ( object && object.isObject3D ) ) { + + if ( object.parent !== null ) { + + object.parent.remove( object ); + + } + + object.parent = this; + object.dispatchEvent( { type: 'added' } ); + + this.children.push( object ); + + } else { + + console.error( "THREE.Object3D.add: object not an instance of THREE.Object3D.", object ); + + } + + return this; + + }, + + remove: function ( object ) { + + if ( arguments.length > 1 ) { + + for ( var i = 0; i < arguments.length; i ++ ) { + + this.remove( arguments[ i ] ); + + } + + return this; + + } + + var index = this.children.indexOf( object ); + + if ( index !== - 1 ) { + + object.parent = null; + + object.dispatchEvent( { type: 'removed' } ); + + this.children.splice( index, 1 ); + + } + + return this; + + }, + + getObjectById: function ( id ) { + + return this.getObjectByProperty( 'id', id ); + + }, + + getObjectByName: function ( name ) { + + return this.getObjectByProperty( 'name', name ); + + }, + + getObjectByProperty: function ( name, value ) { + + if ( this[ name ] === value ) return this; + + for ( var i = 0, l = this.children.length; i < l; i ++ ) { + + var child = this.children[ i ]; + var object = child.getObjectByProperty( name, value ); + + if ( object !== undefined ) { + + return object; + + } + + } + + return undefined; + + }, + + getWorldPosition: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + this.updateMatrixWorld( true ); + + return result.setFromMatrixPosition( this.matrixWorld ); + + }, + + getWorldQuaternion: function () { + + var position = new Vector3(); + var scale = new Vector3(); + + return function getWorldQuaternion( optionalTarget ) { + + var result = optionalTarget || new Quaternion(); + + this.updateMatrixWorld( true ); + + this.matrixWorld.decompose( position, result, scale ); + + return result; + + }; + + }(), + + getWorldRotation: function () { + + var quaternion = new Quaternion(); + + return function getWorldRotation( optionalTarget ) { + + var result = optionalTarget || new Euler(); + + this.getWorldQuaternion( quaternion ); + + return result.setFromQuaternion( quaternion, this.rotation.order, false ); + + }; + + }(), + + getWorldScale: function () { + + var position = new Vector3(); + var quaternion = new Quaternion(); + + return function getWorldScale( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + this.updateMatrixWorld( true ); + + this.matrixWorld.decompose( position, quaternion, result ); + + return result; + + }; + + }(), + + getWorldDirection: function () { + + var quaternion = new Quaternion(); + + return function getWorldDirection( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + this.getWorldQuaternion( quaternion ); + + return result.set( 0, 0, 1 ).applyQuaternion( quaternion ); + + }; + + }(), + + raycast: function () {}, + + traverse: function ( callback ) { + + callback( this ); + + var children = this.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].traverse( callback ); + + } + + }, + + traverseVisible: function ( callback ) { + + if ( this.visible === false ) return; + + callback( this ); + + var children = this.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].traverseVisible( callback ); + + } + + }, + + traverseAncestors: function ( callback ) { + + var parent = this.parent; + + if ( parent !== null ) { + + callback( parent ); + + parent.traverseAncestors( callback ); + + } + + }, + + updateMatrix: function () { + + this.matrix.compose( this.position, this.quaternion, this.scale ); + + this.matrixWorldNeedsUpdate = true; + + }, + + updateMatrixWorld: function ( force ) { + + if ( this.matrixAutoUpdate ) this.updateMatrix(); + + if ( this.matrixWorldNeedsUpdate || force ) { + + if ( this.parent === null ) { + + this.matrixWorld.copy( this.matrix ); + + } else { + + this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix ); + + } + + this.matrixWorldNeedsUpdate = false; + + force = true; + + } + + // update children + + var children = this.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + children[ i ].updateMatrixWorld( force ); + + } + + }, + + toJSON: function ( meta ) { + + // meta is a string when called from JSON.stringify + var isRootObject = ( meta === undefined || typeof meta === 'string' ); + + var output = {}; + + // meta is a hash used to collect geometries, materials. + // not providing it implies that this is the root object + // being serialized. + if ( isRootObject ) { + + // initialize meta obj + meta = { + geometries: {}, + materials: {}, + textures: {}, + images: {}, + shapes: {} + }; + + output.metadata = { + version: 4.5, + type: 'Object', + generator: 'Object3D.toJSON' + }; + + } + + // standard Object3D serialization + + var object = {}; + + object.uuid = this.uuid; + object.type = this.type; + + if ( this.name !== '' ) object.name = this.name; + if ( this.castShadow === true ) object.castShadow = true; + if ( this.receiveShadow === true ) object.receiveShadow = true; + if ( this.visible === false ) object.visible = false; + if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData; + + object.matrix = this.matrix.toArray(); + + // + + function serialize( library, element ) { + + if ( library[ element.uuid ] === undefined ) { + + library[ element.uuid ] = element.toJSON( meta ); + + } + + return element.uuid; + + } + + if ( this.geometry !== undefined ) { + + object.geometry = serialize( meta.geometries, this.geometry ); + + var parameters = this.geometry.parameters; + + if ( parameters !== undefined && parameters.shapes !== undefined ) { + + var shapes = parameters.shapes; + + if ( Array.isArray( shapes ) ) { + + for ( var i = 0, l = shapes.length; i < l; i ++ ) { + + var shape = shapes[ i ]; + + serialize( meta.shapes, shape ); + + } + + } else { + + serialize( meta.shapes, shapes ); + + } + + } + + } + + if ( this.material !== undefined ) { + + if ( Array.isArray( this.material ) ) { + + var uuids = []; + + for ( var i = 0, l = this.material.length; i < l; i ++ ) { + + uuids.push( serialize( meta.materials, this.material[ i ] ) ); + + } + + object.material = uuids; + + } else { + + object.material = serialize( meta.materials, this.material ); + + } + + } + + // + + if ( this.children.length > 0 ) { + + object.children = []; + + for ( var i = 0; i < this.children.length; i ++ ) { + + object.children.push( this.children[ i ].toJSON( meta ).object ); + + } + + } + + if ( isRootObject ) { + + var geometries = extractFromCache( meta.geometries ); + var materials = extractFromCache( meta.materials ); + var textures = extractFromCache( meta.textures ); + var images = extractFromCache( meta.images ); + var shapes = extractFromCache( meta.shapes ); + + if ( geometries.length > 0 ) output.geometries = geometries; + if ( materials.length > 0 ) output.materials = materials; + if ( textures.length > 0 ) output.textures = textures; + if ( images.length > 0 ) output.images = images; + if ( shapes.length > 0 ) output.shapes = shapes; + + } + + output.object = object; + + return output; + + // extract data from the cache hash + // remove metadata on each item + // and return as array + function extractFromCache( cache ) { + + var values = []; + for ( var key in cache ) { + + var data = cache[ key ]; + delete data.metadata; + values.push( data ); + + } + return values; + + } + + }, + + clone: function ( recursive ) { + + return new this.constructor().copy( this, recursive ); + + }, + + copy: function ( source, recursive ) { + + if ( recursive === undefined ) recursive = true; + + this.name = source.name; + + this.up.copy( source.up ); + + this.position.copy( source.position ); + this.quaternion.copy( source.quaternion ); + this.scale.copy( source.scale ); + + this.matrix.copy( source.matrix ); + this.matrixWorld.copy( source.matrixWorld ); + + this.matrixAutoUpdate = source.matrixAutoUpdate; + this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate; + + this.layers.mask = source.layers.mask; + this.visible = source.visible; + + this.castShadow = source.castShadow; + this.receiveShadow = source.receiveShadow; + + this.frustumCulled = source.frustumCulled; + this.renderOrder = source.renderOrder; + + this.userData = JSON.parse( JSON.stringify( source.userData ) ); + + if ( recursive === true ) { + + for ( var i = 0; i < source.children.length; i ++ ) { + + var child = source.children[ i ]; + this.add( child.clone() ); + + } + + } + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author mikael emtinger / http://gomo.se/ + * @author WestLangley / http://github.com/WestLangley + */ + + function Camera() { + + Object3D.call( this ); + + this.type = 'Camera'; + + this.matrixWorldInverse = new Matrix4(); + this.projectionMatrix = new Matrix4(); + + } + + Camera.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Camera, + + isCamera: true, + + copy: function ( source, recursive ) { + + Object3D.prototype.copy.call( this, source, recursive ); + + this.matrixWorldInverse.copy( source.matrixWorldInverse ); + this.projectionMatrix.copy( source.projectionMatrix ); + + return this; + + }, + + getWorldDirection: function () { + + var quaternion = new Quaternion(); + + return function getWorldDirection( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + this.getWorldQuaternion( quaternion ); + + return result.set( 0, 0, - 1 ).applyQuaternion( quaternion ); + + }; + + }(), + + updateMatrixWorld: function ( force ) { + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + this.matrixWorldInverse.getInverse( this.matrixWorld ); + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author arose / http://github.com/arose + */ + + function OrthographicCamera( left, right, top, bottom, near, far ) { + + Camera.call( this ); + + this.type = 'OrthographicCamera'; + + this.zoom = 1; + this.view = null; + + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; + + this.near = ( near !== undefined ) ? near : 0.1; + this.far = ( far !== undefined ) ? far : 2000; + + this.updateProjectionMatrix(); + + } + + OrthographicCamera.prototype = Object.assign( Object.create( Camera.prototype ), { + + constructor: OrthographicCamera, + + isOrthographicCamera: true, + + copy: function ( source, recursive ) { + + Camera.prototype.copy.call( this, source, recursive ); + + this.left = source.left; + this.right = source.right; + this.top = source.top; + this.bottom = source.bottom; + this.near = source.near; + this.far = source.far; + + this.zoom = source.zoom; + this.view = source.view === null ? null : Object.assign( {}, source.view ); + + return this; + + }, + + setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) { + + if ( this.view === null ) { + + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; + + } + + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; + + this.updateProjectionMatrix(); + + }, + + clearViewOffset: function () { + + if ( this.view !== null ) { + + this.view.enabled = false; + + } + + this.updateProjectionMatrix(); + + }, + + updateProjectionMatrix: function () { + + var dx = ( this.right - this.left ) / ( 2 * this.zoom ); + var dy = ( this.top - this.bottom ) / ( 2 * this.zoom ); + var cx = ( this.right + this.left ) / 2; + var cy = ( this.top + this.bottom ) / 2; + + var left = cx - dx; + var right = cx + dx; + var top = cy + dy; + var bottom = cy - dy; + + if ( this.view !== null && this.view.enabled ) { + + var zoomW = this.zoom / ( this.view.width / this.view.fullWidth ); + var zoomH = this.zoom / ( this.view.height / this.view.fullHeight ); + var scaleW = ( this.right - this.left ) / this.view.width; + var scaleH = ( this.top - this.bottom ) / this.view.height; + + left += scaleW * ( this.view.offsetX / zoomW ); + right = left + scaleW * ( this.view.width / zoomW ); + top -= scaleH * ( this.view.offsetY / zoomH ); + bottom = top - scaleH * ( this.view.height / zoomH ); + + } + + this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far ); + + }, + + toJSON: function ( meta ) { + + var data = Object3D.prototype.toJSON.call( this, meta ); + + data.object.zoom = this.zoom; + data.object.left = this.left; + data.object.right = this.right; + data.object.top = this.top; + data.object.bottom = this.bottom; + data.object.near = this.near; + data.object.far = this.far; + + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + + return data; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function Face3( a, b, c, normal, color, materialIndex ) { + + this.a = a; + this.b = b; + this.c = c; + + this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3(); + this.vertexNormals = Array.isArray( normal ) ? normal : []; + + this.color = ( color && color.isColor ) ? color : new Color(); + this.vertexColors = Array.isArray( color ) ? color : []; + + this.materialIndex = materialIndex !== undefined ? materialIndex : 0; + + } + + Object.assign( Face3.prototype, { + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.a = source.a; + this.b = source.b; + this.c = source.c; + + this.normal.copy( source.normal ); + this.color.copy( source.color ); + + this.materialIndex = source.materialIndex; + + for ( var i = 0, il = source.vertexNormals.length; i < il; i ++ ) { + + this.vertexNormals[ i ] = source.vertexNormals[ i ].clone(); + + } + + for ( var i = 0, il = source.vertexColors.length; i < il; i ++ ) { + + this.vertexColors[ i ] = source.vertexColors[ i ].clone(); + + } + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author kile / http://kile.stravaganza.org/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author bhouston / http://clara.io + */ + + var geometryId = 0; // Geometry uses even numbers as Id + + function Geometry() { + + Object.defineProperty( this, 'id', { value: geometryId += 2 } ); + + this.uuid = _Math.generateUUID(); + + this.name = ''; + this.type = 'Geometry'; + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + + this.morphTargets = []; + this.morphNormals = []; + + this.skinWeights = []; + this.skinIndices = []; + + this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.elementsNeedUpdate = false; + this.verticesNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: Geometry, + + isGeometry: true, + + applyMatrix: function ( matrix ) { + + var normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + for ( var i = 0, il = this.vertices.length; i < il; i ++ ) { + + var vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); + + } + + for ( var i = 0, il = this.faces.length; i < il; i ++ ) { + + var face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); + + } + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + this.verticesNeedUpdate = true; + this.normalsNeedUpdate = true; + + return this; + + }, + + rotateX: function () { + + // rotate geometry around world x-axis + + var m1 = new Matrix4(); + + return function rotateX( angle ) { + + m1.makeRotationX( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + rotateY: function () { + + // rotate geometry around world y-axis + + var m1 = new Matrix4(); + + return function rotateY( angle ) { + + m1.makeRotationY( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + rotateZ: function () { + + // rotate geometry around world z-axis + + var m1 = new Matrix4(); + + return function rotateZ( angle ) { + + m1.makeRotationZ( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + translate: function () { + + // translate geometry + + var m1 = new Matrix4(); + + return function translate( x, y, z ) { + + m1.makeTranslation( x, y, z ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + scale: function () { + + // scale geometry + + var m1 = new Matrix4(); + + return function scale( x, y, z ) { + + m1.makeScale( x, y, z ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + lookAt: function () { + + var obj = new Object3D(); + + return function lookAt( vector ) { + + obj.lookAt( vector ); + + obj.updateMatrix(); + + this.applyMatrix( obj.matrix ); + + }; + + }(), + + fromBufferGeometry: function ( geometry ) { + + var scope = this; + + var indices = geometry.index !== null ? geometry.index.array : undefined; + var attributes = geometry.attributes; + + var positions = attributes.position.array; + var normals = attributes.normal !== undefined ? attributes.normal.array : undefined; + var colors = attributes.color !== undefined ? attributes.color.array : undefined; + var uvs = attributes.uv !== undefined ? attributes.uv.array : undefined; + var uvs2 = attributes.uv2 !== undefined ? attributes.uv2.array : undefined; + + if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = []; + + var tempNormals = []; + var tempUVs = []; + var tempUVs2 = []; + + for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) { + + scope.vertices.push( new Vector3( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] ) ); + + if ( normals !== undefined ) { + + tempNormals.push( new Vector3( normals[ i ], normals[ i + 1 ], normals[ i + 2 ] ) ); + + } + + if ( colors !== undefined ) { + + scope.colors.push( new Color( colors[ i ], colors[ i + 1 ], colors[ i + 2 ] ) ); + + } + + if ( uvs !== undefined ) { + + tempUVs.push( new Vector2( uvs[ j ], uvs[ j + 1 ] ) ); + + } + + if ( uvs2 !== undefined ) { + + tempUVs2.push( new Vector2( uvs2[ j ], uvs2[ j + 1 ] ) ); + + } + + } + + function addFace( a, b, c, materialIndex ) { + + var vertexNormals = normals !== undefined ? [ tempNormals[ a ].clone(), tempNormals[ b ].clone(), tempNormals[ c ].clone() ] : []; + var vertexColors = colors !== undefined ? [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ] : []; + + var face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex ); + + scope.faces.push( face ); + + if ( uvs !== undefined ) { + + scope.faceVertexUvs[ 0 ].push( [ tempUVs[ a ].clone(), tempUVs[ b ].clone(), tempUVs[ c ].clone() ] ); + + } + + if ( uvs2 !== undefined ) { + + scope.faceVertexUvs[ 1 ].push( [ tempUVs2[ a ].clone(), tempUVs2[ b ].clone(), tempUVs2[ c ].clone() ] ); + + } + + } + + var groups = geometry.groups; + + if ( groups.length > 0 ) { + + for ( var i = 0; i < groups.length; i ++ ) { + + var group = groups[ i ]; + + var start = group.start; + var count = group.count; + + for ( var j = start, jl = start + count; j < jl; j += 3 ) { + + if ( indices !== undefined ) { + + addFace( indices[ j ], indices[ j + 1 ], indices[ j + 2 ], group.materialIndex ); + + } else { + + addFace( j, j + 1, j + 2, group.materialIndex ); + + } + + } + + } + + } else { + + if ( indices !== undefined ) { + + for ( var i = 0; i < indices.length; i += 3 ) { + + addFace( indices[ i ], indices[ i + 1 ], indices[ i + 2 ] ); + + } + + } else { + + for ( var i = 0; i < positions.length / 3; i += 3 ) { + + addFace( i, i + 1, i + 2 ); + + } + + } + + } + + this.computeFaceNormals(); + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + return this; + + }, + + center: function () { + + this.computeBoundingBox(); + + var offset = this.boundingBox.getCenter().negate(); + + this.translate( offset.x, offset.y, offset.z ); + + return offset; + + }, + + normalize: function () { + + this.computeBoundingSphere(); + + var center = this.boundingSphere.center; + var radius = this.boundingSphere.radius; + + var s = radius === 0 ? 1 : 1.0 / radius; + + var matrix = new Matrix4(); + matrix.set( + s, 0, 0, - s * center.x, + 0, s, 0, - s * center.y, + 0, 0, s, - s * center.z, + 0, 0, 0, 1 + ); + + this.applyMatrix( matrix ); + + return this; + + }, + + computeFaceNormals: function () { + + var cb = new Vector3(), ab = new Vector3(); + + for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) { + + var face = this.faces[ f ]; + + var vA = this.vertices[ face.a ]; + var vB = this.vertices[ face.b ]; + var vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + face.normal.copy( cb ); + + } + + }, + + computeVertexNormals: function ( areaWeighted ) { + + if ( areaWeighted === undefined ) areaWeighted = true; + + var v, vl, f, fl, face, vertices; + + vertices = new Array( this.vertices.length ); + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ] = new Vector3(); + + } + + if ( areaWeighted ) { + + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm + + var vA, vB, vC; + var cb = new Vector3(), ab = new Vector3(); + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vA = this.vertices[ face.a ]; + vB = this.vertices[ face.b ]; + vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); + + } + + } else { + + this.computeFaceNormals(); + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); + + } + + } + + for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ].normalize(); + + } + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + var vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( vertices[ face.a ] ); + vertexNormals[ 1 ].copy( vertices[ face.b ] ); + vertexNormals[ 2 ].copy( vertices[ face.c ] ); + + } else { + + vertexNormals[ 0 ] = vertices[ face.a ].clone(); + vertexNormals[ 1 ] = vertices[ face.b ].clone(); + vertexNormals[ 2 ] = vertices[ face.c ].clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + }, + + computeFlatVertexNormals: function () { + + var f, fl, face; + + this.computeFaceNormals(); + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + var vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( face.normal ); + vertexNormals[ 1 ].copy( face.normal ); + vertexNormals[ 2 ].copy( face.normal ); + + } else { + + vertexNormals[ 0 ] = face.normal.clone(); + vertexNormals[ 1 ] = face.normal.clone(); + vertexNormals[ 2 ] = face.normal.clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + }, + + computeMorphNormals: function () { + + var i, il, f, fl, face; + + // save original normals + // - create temp variables on first access + // otherwise just copy (for faster repeated calls) + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + if ( ! face.__originalFaceNormal ) { + + face.__originalFaceNormal = face.normal.clone(); + + } else { + + face.__originalFaceNormal.copy( face.normal ); + + } + + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; + + for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) { + + if ( ! face.__originalVertexNormals[ i ] ) { + + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); + + } else { + + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); + + } + + } + + } + + // use temp geometry to compute face and vertex normals for each morph + + var tmpGeo = new Geometry(); + tmpGeo.faces = this.faces; + + for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) { + + // create on first access + + if ( ! this.morphNormals[ i ] ) { + + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; + + var dstNormalsFace = this.morphNormals[ i ].faceNormals; + var dstNormalsVertex = this.morphNormals[ i ].vertexNormals; + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + faceNormal = new Vector3(); + vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() }; + + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); + + } + + } + + var morphNormals = this.morphNormals[ i ]; + + // set vertices to morph target + + tmpGeo.vertices = this.morphTargets[ i ].vertices; + + // compute morph normals + + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); + + // store morph normals + + var faceNormal, vertexNormals; + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + faceNormal = morphNormals.faceNormals[ f ]; + vertexNormals = morphNormals.vertexNormals[ f ]; + + faceNormal.copy( face.normal ); + + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); + + } + + } + + // restore original normals + + for ( f = 0, fl = this.faces.length; f < fl; f ++ ) { + + face = this.faces[ f ]; + + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; + + } + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + this.boundingBox.setFromPoints( this.vertices ); + + }, + + computeBoundingSphere: function () { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + this.boundingSphere.setFromPoints( this.vertices ); + + }, + + merge: function ( geometry, matrix, materialIndexOffset ) { + + if ( ! ( geometry && geometry.isGeometry ) ) { + + console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); + return; + + } + + var normalMatrix, + vertexOffset = this.vertices.length, + vertices1 = this.vertices, + vertices2 = geometry.vertices, + faces1 = this.faces, + faces2 = geometry.faces, + uvs1 = this.faceVertexUvs[ 0 ], + uvs2 = geometry.faceVertexUvs[ 0 ], + colors1 = this.colors, + colors2 = geometry.colors; + + if ( materialIndexOffset === undefined ) materialIndexOffset = 0; + + if ( matrix !== undefined ) { + + normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + } + + // vertices + + for ( var i = 0, il = vertices2.length; i < il; i ++ ) { + + var vertex = vertices2[ i ]; + + var vertexCopy = vertex.clone(); + + if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); + + vertices1.push( vertexCopy ); + + } + + // colors + + for ( var i = 0, il = colors2.length; i < il; i ++ ) { + + colors1.push( colors2[ i ].clone() ); + + } + + // faces + + for ( i = 0, il = faces2.length; i < il; i ++ ) { + + var face = faces2[ i ], faceCopy, normal, color, + faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; + + faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); + + if ( normalMatrix !== undefined ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix !== undefined ) { + + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + faceCopy.vertexNormals.push( normal ); + + } + + faceCopy.color.copy( face.color ); + + for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { + + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); + + } + + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; + + faces1.push( faceCopy ); + + } + + // uvs + + for ( i = 0, il = uvs2.length; i < il; i ++ ) { + + var uv = uvs2[ i ], uvCopy = []; + + if ( uv === undefined ) { + + continue; + + } + + for ( var j = 0, jl = uv.length; j < jl; j ++ ) { + + uvCopy.push( uv[ j ].clone() ); + + } + + uvs1.push( uvCopy ); + + } + + }, + + mergeMesh: function ( mesh ) { + + if ( ! ( mesh && mesh.isMesh ) ) { + + console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh ); + return; + + } + + mesh.matrixAutoUpdate && mesh.updateMatrix(); + + this.merge( mesh.geometry, mesh.matrix ); + + }, + + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ + + mergeVertices: function () { + + var verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique) + var unique = [], changes = []; + + var v, key; + var precisionPoints = 4; // number of decimal points, e.g. 4 for epsilon of 0.0001 + var precision = Math.pow( 10, precisionPoints ); + var i, il, face; + var indices, j, jl; + + for ( i = 0, il = this.vertices.length; i < il; i ++ ) { + + v = this.vertices[ i ]; + key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); + + if ( verticesMap[ key ] === undefined ) { + + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; + + } else { + + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; + + } + + } + + + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + var faceIndicesToRemove = []; + + for ( i = 0, il = this.faces.length; i < il; i ++ ) { + + face = this.faces[ i ]; + + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; + + indices = [ face.a, face.b, face.c ]; + + // if any duplicate vertices are found in a Face3 + // we have to remove the face as nothing can be saved + for ( var n = 0; n < 3; n ++ ) { + + if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) { + + faceIndicesToRemove.push( i ); + break; + + } + + } + + } + + for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { + + var idx = faceIndicesToRemove[ i ]; + + this.faces.splice( idx, 1 ); + + for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { + + this.faceVertexUvs[ j ].splice( idx, 1 ); + + } + + } + + // Use unique set of vertices + + var diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; + + }, + + setFromPoints: function ( points ) { + + this.vertices = []; + + for ( var i = 0, l = points.length; i < l; i ++ ) { + + var point = points[ i ]; + this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); + + } + + return this; + + }, + + sortFacesByMaterialIndex: function () { + + var faces = this.faces; + var length = faces.length; + + // tag faces + + for ( var i = 0; i < length; i ++ ) { + + faces[ i ]._id = i; + + } + + // sort faces + + function materialIndexSort( a, b ) { + + return a.materialIndex - b.materialIndex; + + } + + faces.sort( materialIndexSort ); + + // sort uvs + + var uvs1 = this.faceVertexUvs[ 0 ]; + var uvs2 = this.faceVertexUvs[ 1 ]; + + var newUvs1, newUvs2; + + if ( uvs1 && uvs1.length === length ) newUvs1 = []; + if ( uvs2 && uvs2.length === length ) newUvs2 = []; + + for ( var i = 0; i < length; i ++ ) { + + var id = faces[ i ]._id; + + if ( newUvs1 ) newUvs1.push( uvs1[ id ] ); + if ( newUvs2 ) newUvs2.push( uvs2[ id ] ); + + } + + if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1; + if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2; + + }, + + toJSON: function () { + + var data = { + metadata: { + version: 4.5, + type: 'Geometry', + generator: 'Geometry.toJSON' + } + }; + + // standard Geometry serialization + + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + + if ( this.parameters !== undefined ) { + + var parameters = this.parameters; + + for ( var key in parameters ) { + + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + + } + + return data; + + } + + var vertices = []; + + for ( var i = 0; i < this.vertices.length; i ++ ) { + + var vertex = this.vertices[ i ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + var faces = []; + var normals = []; + var normalsHash = {}; + var colors = []; + var colorsHash = {}; + var uvs = []; + var uvsHash = {}; + + for ( var i = 0; i < this.faces.length; i ++ ) { + + var face = this.faces[ i ]; + + var hasMaterial = true; + var hasFaceUv = false; // deprecated + var hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined; + var hasFaceNormal = face.normal.length() > 0; + var hasFaceVertexNormal = face.vertexNormals.length > 0; + var hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; + var hasFaceVertexColor = face.vertexColors.length > 0; + + var faceType = 0; + + faceType = setBit( faceType, 0, 0 ); // isQuad + faceType = setBit( faceType, 1, hasMaterial ); + faceType = setBit( faceType, 2, hasFaceUv ); + faceType = setBit( faceType, 3, hasFaceVertexUv ); + faceType = setBit( faceType, 4, hasFaceNormal ); + faceType = setBit( faceType, 5, hasFaceVertexNormal ); + faceType = setBit( faceType, 6, hasFaceColor ); + faceType = setBit( faceType, 7, hasFaceVertexColor ); + + faces.push( faceType ); + faces.push( face.a, face.b, face.c ); + faces.push( face.materialIndex ); + + if ( hasFaceVertexUv ) { + + var faceVertexUvs = this.faceVertexUvs[ 0 ][ i ]; + + faces.push( + getUvIndex( faceVertexUvs[ 0 ] ), + getUvIndex( faceVertexUvs[ 1 ] ), + getUvIndex( faceVertexUvs[ 2 ] ) + ); + + } + + if ( hasFaceNormal ) { + + faces.push( getNormalIndex( face.normal ) ); + + } + + if ( hasFaceVertexNormal ) { + + var vertexNormals = face.vertexNormals; + + faces.push( + getNormalIndex( vertexNormals[ 0 ] ), + getNormalIndex( vertexNormals[ 1 ] ), + getNormalIndex( vertexNormals[ 2 ] ) + ); + + } + + if ( hasFaceColor ) { + + faces.push( getColorIndex( face.color ) ); + + } + + if ( hasFaceVertexColor ) { + + var vertexColors = face.vertexColors; + + faces.push( + getColorIndex( vertexColors[ 0 ] ), + getColorIndex( vertexColors[ 1 ] ), + getColorIndex( vertexColors[ 2 ] ) + ); + + } + + } + + function setBit( value, position, enabled ) { + + return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) ); + + } + + function getNormalIndex( normal ) { + + var hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); + + if ( normalsHash[ hash ] !== undefined ) { + + return normalsHash[ hash ]; + + } + + normalsHash[ hash ] = normals.length / 3; + normals.push( normal.x, normal.y, normal.z ); + + return normalsHash[ hash ]; + + } + + function getColorIndex( color ) { + + var hash = color.r.toString() + color.g.toString() + color.b.toString(); + + if ( colorsHash[ hash ] !== undefined ) { + + return colorsHash[ hash ]; + + } + + colorsHash[ hash ] = colors.length; + colors.push( color.getHex() ); + + return colorsHash[ hash ]; + + } + + function getUvIndex( uv ) { + + var hash = uv.x.toString() + uv.y.toString(); + + if ( uvsHash[ hash ] !== undefined ) { + + return uvsHash[ hash ]; + + } + + uvsHash[ hash ] = uvs.length / 2; + uvs.push( uv.x, uv.y ); + + return uvsHash[ hash ]; + + } + + data.data = {}; + + data.data.vertices = vertices; + data.data.normals = normals; + if ( colors.length > 0 ) data.data.colors = colors; + if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility + data.data.faces = faces; + + return data; + + }, + + clone: function () { + + /* + // Handle primitives + + var parameters = this.parameters; + + if ( parameters !== undefined ) { + + var values = []; + + for ( var key in parameters ) { + + values.push( parameters[ key ] ); + + } + + var geometry = Object.create( this.constructor.prototype ); + this.constructor.apply( geometry, values ); + return geometry; + + } + + return new this.constructor().copy( this ); + */ + + return new Geometry().copy( this ); + + }, + + copy: function ( source ) { + + var i, il, j, jl, k, kl; + + // reset + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + this.morphTargets = []; + this.morphNormals = []; + this.skinWeights = []; + this.skinIndices = []; + this.lineDistances = []; + this.boundingBox = null; + this.boundingSphere = null; + + // name + + this.name = source.name; + + // vertices + + var vertices = source.vertices; + + for ( i = 0, il = vertices.length; i < il; i ++ ) { + + this.vertices.push( vertices[ i ].clone() ); + + } + + // colors + + var colors = source.colors; + + for ( i = 0, il = colors.length; i < il; i ++ ) { + + this.colors.push( colors[ i ].clone() ); + + } + + // faces + + var faces = source.faces; + + for ( i = 0, il = faces.length; i < il; i ++ ) { + + this.faces.push( faces[ i ].clone() ); + + } + + // face vertex uvs + + for ( i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) { + + var faceVertexUvs = source.faceVertexUvs[ i ]; + + if ( this.faceVertexUvs[ i ] === undefined ) { + + this.faceVertexUvs[ i ] = []; + + } + + for ( j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) { + + var uvs = faceVertexUvs[ j ], uvsCopy = []; + + for ( k = 0, kl = uvs.length; k < kl; k ++ ) { + + var uv = uvs[ k ]; + + uvsCopy.push( uv.clone() ); + + } + + this.faceVertexUvs[ i ].push( uvsCopy ); + + } + + } + + // morph targets + + var morphTargets = source.morphTargets; + + for ( i = 0, il = morphTargets.length; i < il; i ++ ) { + + var morphTarget = {}; + morphTarget.name = morphTargets[ i ].name; + + // vertices + + if ( morphTargets[ i ].vertices !== undefined ) { + + morphTarget.vertices = []; + + for ( j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) { + + morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() ); + + } + + } + + // normals + + if ( morphTargets[ i ].normals !== undefined ) { + + morphTarget.normals = []; + + for ( j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) { + + morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() ); + + } + + } + + this.morphTargets.push( morphTarget ); + + } + + // morph normals + + var morphNormals = source.morphNormals; + + for ( i = 0, il = morphNormals.length; i < il; i ++ ) { + + var morphNormal = {}; + + // vertex normals + + if ( morphNormals[ i ].vertexNormals !== undefined ) { + + morphNormal.vertexNormals = []; + + for ( j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) { + + var srcVertexNormal = morphNormals[ i ].vertexNormals[ j ]; + var destVertexNormal = {}; + + destVertexNormal.a = srcVertexNormal.a.clone(); + destVertexNormal.b = srcVertexNormal.b.clone(); + destVertexNormal.c = srcVertexNormal.c.clone(); + + morphNormal.vertexNormals.push( destVertexNormal ); + + } + + } + + // face normals + + if ( morphNormals[ i ].faceNormals !== undefined ) { + + morphNormal.faceNormals = []; + + for ( j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) { + + morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() ); + + } + + } + + this.morphNormals.push( morphNormal ); + + } + + // skin weights + + var skinWeights = source.skinWeights; + + for ( i = 0, il = skinWeights.length; i < il; i ++ ) { + + this.skinWeights.push( skinWeights[ i ].clone() ); + + } + + // skin indices + + var skinIndices = source.skinIndices; + + for ( i = 0, il = skinIndices.length; i < il; i ++ ) { + + this.skinIndices.push( skinIndices[ i ].clone() ); + + } + + // line distances + + var lineDistances = source.lineDistances; + + for ( i = 0, il = lineDistances.length; i < il; i ++ ) { + + this.lineDistances.push( lineDistances[ i ] ); + + } + + // bounding box + + var boundingBox = source.boundingBox; + + if ( boundingBox !== null ) { + + this.boundingBox = boundingBox.clone(); + + } + + // bounding sphere + + var boundingSphere = source.boundingSphere; + + if ( boundingSphere !== null ) { + + this.boundingSphere = boundingSphere.clone(); + + } + + // update flags + + this.elementsNeedUpdate = source.elementsNeedUpdate; + this.verticesNeedUpdate = source.verticesNeedUpdate; + this.uvsNeedUpdate = source.uvsNeedUpdate; + this.normalsNeedUpdate = source.normalsNeedUpdate; + this.colorsNeedUpdate = source.colorsNeedUpdate; + this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate; + this.groupsNeedUpdate = source.groupsNeedUpdate; + + return this; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function BufferAttribute( array, itemSize, normalized ) { + + if ( Array.isArray( array ) ) { + + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + + } + + this.uuid = _Math.generateUUID(); + this.name = ''; + + this.array = array; + this.itemSize = itemSize; + this.count = array !== undefined ? array.length / itemSize : 0; + this.normalized = normalized === true; + + this.dynamic = false; + this.updateRange = { offset: 0, count: - 1 }; + + this.onUploadCallback = function () {}; + + this.version = 0; + + } + + Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', { + + set: function ( value ) { + + if ( value === true ) this.version ++; + + } + + } ); + + Object.assign( BufferAttribute.prototype, { + + isBufferAttribute: true, + + setArray: function ( array ) { + + if ( Array.isArray( array ) ) { + + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + + } + + this.count = array !== undefined ? array.length / this.itemSize : 0; + this.array = array; + + }, + + setDynamic: function ( value ) { + + this.dynamic = value; + + return this; + + }, + + copy: function ( source ) { + + this.array = new source.array.constructor( source.array ); + this.itemSize = source.itemSize; + this.count = source.count; + this.normalized = source.normalized; + + this.dynamic = source.dynamic; + + return this; + + }, + + copyAt: function ( index1, attribute, index2 ) { + + index1 *= this.itemSize; + index2 *= attribute.itemSize; + + for ( var i = 0, l = this.itemSize; i < l; i ++ ) { + + this.array[ index1 + i ] = attribute.array[ index2 + i ]; + + } + + return this; + + }, + + copyArray: function ( array ) { + + this.array.set( array ); + + return this; + + }, + + copyColorsArray: function ( colors ) { + + var array = this.array, offset = 0; + + for ( var i = 0, l = colors.length; i < l; i ++ ) { + + var color = colors[ i ]; + + if ( color === undefined ) { + + console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i ); + color = new Color(); + + } + + array[ offset ++ ] = color.r; + array[ offset ++ ] = color.g; + array[ offset ++ ] = color.b; + + } + + return this; + + }, + + copyVector2sArray: function ( vectors ) { + + var array = this.array, offset = 0; + + for ( var i = 0, l = vectors.length; i < l; i ++ ) { + + var vector = vectors[ i ]; + + if ( vector === undefined ) { + + console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i ); + vector = new Vector2(); + + } + + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; + + } + + return this; + + }, + + copyVector3sArray: function ( vectors ) { + + var array = this.array, offset = 0; + + for ( var i = 0, l = vectors.length; i < l; i ++ ) { + + var vector = vectors[ i ]; + + if ( vector === undefined ) { + + console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i ); + vector = new Vector3(); + + } + + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; + array[ offset ++ ] = vector.z; + + } + + return this; + + }, + + copyVector4sArray: function ( vectors ) { + + var array = this.array, offset = 0; + + for ( var i = 0, l = vectors.length; i < l; i ++ ) { + + var vector = vectors[ i ]; + + if ( vector === undefined ) { + + console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i ); + vector = new Vector4(); + + } + + array[ offset ++ ] = vector.x; + array[ offset ++ ] = vector.y; + array[ offset ++ ] = vector.z; + array[ offset ++ ] = vector.w; + + } + + return this; + + }, + + set: function ( value, offset ) { + + if ( offset === undefined ) offset = 0; + + this.array.set( value, offset ); + + return this; + + }, + + getX: function ( index ) { + + return this.array[ index * this.itemSize ]; + + }, + + setX: function ( index, x ) { + + this.array[ index * this.itemSize ] = x; + + return this; + + }, + + getY: function ( index ) { + + return this.array[ index * this.itemSize + 1 ]; + + }, + + setY: function ( index, y ) { + + this.array[ index * this.itemSize + 1 ] = y; + + return this; + + }, + + getZ: function ( index ) { + + return this.array[ index * this.itemSize + 2 ]; + + }, + + setZ: function ( index, z ) { + + this.array[ index * this.itemSize + 2 ] = z; + + return this; + + }, + + getW: function ( index ) { + + return this.array[ index * this.itemSize + 3 ]; + + }, + + setW: function ( index, w ) { + + this.array[ index * this.itemSize + 3 ] = w; + + return this; + + }, + + setXY: function ( index, x, y ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + + return this; + + }, + + setXYZ: function ( index, x, y, z ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + + return this; + + }, + + setXYZW: function ( index, x, y, z, w ) { + + index *= this.itemSize; + + this.array[ index + 0 ] = x; + this.array[ index + 1 ] = y; + this.array[ index + 2 ] = z; + this.array[ index + 3 ] = w; + + return this; + + }, + + onUpload: function ( callback ) { + + this.onUploadCallback = callback; + + return this; + + }, + + clone: function () { + + return new this.constructor( this.array, this.itemSize ).copy( this ); + + } + + } ); + + // + + function Int8BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized ); + + } + + Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Int8BufferAttribute.prototype.constructor = Int8BufferAttribute; + + + function Uint8BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized ); + + } + + Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute; + + + function Uint8ClampedBufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized ); + + } + + Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute; + + + function Int16BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized ); + + } + + Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Int16BufferAttribute.prototype.constructor = Int16BufferAttribute; + + + function Uint16BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized ); + + } + + Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute; + + + function Int32BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized ); + + } + + Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Int32BufferAttribute.prototype.constructor = Int32BufferAttribute; + + + function Uint32BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized ); + + } + + Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute; + + + function Float32BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized ); + + } + + Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Float32BufferAttribute.prototype.constructor = Float32BufferAttribute; + + + function Float64BufferAttribute( array, itemSize, normalized ) { + + BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized ); + + } + + Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype ); + Float64BufferAttribute.prototype.constructor = Float64BufferAttribute; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function DirectGeometry() { + + this.vertices = []; + this.normals = []; + this.colors = []; + this.uvs = []; + this.uvs2 = []; + + this.groups = []; + + this.morphTargets = {}; + + this.skinWeights = []; + this.skinIndices = []; + + // this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.verticesNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + Object.assign( DirectGeometry.prototype, { + + computeGroups: function ( geometry ) { + + var group; + var groups = []; + var materialIndex = undefined; + + var faces = geometry.faces; + + for ( var i = 0; i < faces.length; i ++ ) { + + var face = faces[ i ]; + + // materials + + if ( face.materialIndex !== materialIndex ) { + + materialIndex = face.materialIndex; + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + group = { + start: i * 3, + materialIndex: materialIndex + }; + + } + + } + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + this.groups = groups; + + }, + + fromGeometry: function ( geometry ) { + + var faces = geometry.faces; + var vertices = geometry.vertices; + var faceVertexUvs = geometry.faceVertexUvs; + + var hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0; + var hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; + + // morphs + + var morphTargets = geometry.morphTargets; + var morphTargetsLength = morphTargets.length; + + var morphTargetsPosition; + + if ( morphTargetsLength > 0 ) { + + morphTargetsPosition = []; + + for ( var i = 0; i < morphTargetsLength; i ++ ) { + + morphTargetsPosition[ i ] = []; + + } + + this.morphTargets.position = morphTargetsPosition; + + } + + var morphNormals = geometry.morphNormals; + var morphNormalsLength = morphNormals.length; + + var morphTargetsNormal; + + if ( morphNormalsLength > 0 ) { + + morphTargetsNormal = []; + + for ( var i = 0; i < morphNormalsLength; i ++ ) { + + morphTargetsNormal[ i ] = []; + + } + + this.morphTargets.normal = morphTargetsNormal; + + } + + // skins + + var skinIndices = geometry.skinIndices; + var skinWeights = geometry.skinWeights; + + var hasSkinIndices = skinIndices.length === vertices.length; + var hasSkinWeights = skinWeights.length === vertices.length; + + // + + for ( var i = 0; i < faces.length; i ++ ) { + + var face = faces[ i ]; + + this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] ); + + var vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] ); + + } else { + + var normal = face.normal; + + this.normals.push( normal, normal, normal ); + + } + + var vertexColors = face.vertexColors; + + if ( vertexColors.length === 3 ) { + + this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] ); + + } else { + + var color = face.color; + + this.colors.push( color, color, color ); + + } + + if ( hasFaceVertexUv === true ) { + + var vertexUvs = faceVertexUvs[ 0 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i ); + + this.uvs.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + if ( hasFaceVertexUv2 === true ) { + + var vertexUvs = faceVertexUvs[ 1 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i ); + + this.uvs2.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + // morphs + + for ( var j = 0; j < morphTargetsLength; j ++ ) { + + var morphTarget = morphTargets[ j ].vertices; + + morphTargetsPosition[ j ].push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] ); + + } + + for ( var j = 0; j < morphNormalsLength; j ++ ) { + + var morphNormal = morphNormals[ j ].vertexNormals[ i ]; + + morphTargetsNormal[ j ].push( morphNormal.a, morphNormal.b, morphNormal.c ); + + } + + // skins + + if ( hasSkinIndices ) { + + this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] ); + + } + + if ( hasSkinWeights ) { + + this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] ); + + } + + } + + this.computeGroups( geometry ); + + this.verticesNeedUpdate = geometry.verticesNeedUpdate; + this.normalsNeedUpdate = geometry.normalsNeedUpdate; + this.colorsNeedUpdate = geometry.colorsNeedUpdate; + this.uvsNeedUpdate = geometry.uvsNeedUpdate; + this.groupsNeedUpdate = geometry.groupsNeedUpdate; + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function arrayMax( array ) { + + if ( array.length === 0 ) return - Infinity; + + var max = array[ 0 ]; + + for ( var i = 1, l = array.length; i < l; ++ i ) { + + if ( array[ i ] > max ) max = array[ i ]; + + } + + return max; + + } + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + + var bufferGeometryId = 1; // BufferGeometry uses odd numbers as Id + + function BufferGeometry() { + + Object.defineProperty( this, 'id', { value: bufferGeometryId += 2 } ); + + this.uuid = _Math.generateUUID(); + + this.name = ''; + this.type = 'BufferGeometry'; + + this.index = null; + this.attributes = {}; + + this.morphAttributes = {}; + + this.groups = []; + + this.boundingBox = null; + this.boundingSphere = null; + + this.drawRange = { start: 0, count: Infinity }; + + } + + BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: BufferGeometry, + + isBufferGeometry: true, + + getIndex: function () { + + return this.index; + + }, + + setIndex: function ( index ) { + + if ( Array.isArray( index ) ) { + + this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 ); + + } else { + + this.index = index; + + } + + }, + + addAttribute: function ( name, attribute ) { + + if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) { + + console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' ); + + this.addAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) ); + + return; + + } + + if ( name === 'index' ) { + + console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' ); + this.setIndex( attribute ); + + return; + + } + + this.attributes[ name ] = attribute; + + return this; + + }, + + getAttribute: function ( name ) { + + return this.attributes[ name ]; + + }, + + removeAttribute: function ( name ) { + + delete this.attributes[ name ]; + + return this; + + }, + + addGroup: function ( start, count, materialIndex ) { + + this.groups.push( { + + start: start, + count: count, + materialIndex: materialIndex !== undefined ? materialIndex : 0 + + } ); + + }, + + clearGroups: function () { + + this.groups = []; + + }, + + setDrawRange: function ( start, count ) { + + this.drawRange.start = start; + this.drawRange.count = count; + + }, + + applyMatrix: function ( matrix ) { + + var position = this.attributes.position; + + if ( position !== undefined ) { + + matrix.applyToBufferAttribute( position ); + position.needsUpdate = true; + + } + + var normal = this.attributes.normal; + + if ( normal !== undefined ) { + + var normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + normalMatrix.applyToBufferAttribute( normal ); + normal.needsUpdate = true; + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + return this; + + }, + + rotateX: function () { + + // rotate geometry around world x-axis + + var m1 = new Matrix4(); + + return function rotateX( angle ) { + + m1.makeRotationX( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + rotateY: function () { + + // rotate geometry around world y-axis + + var m1 = new Matrix4(); + + return function rotateY( angle ) { + + m1.makeRotationY( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + rotateZ: function () { + + // rotate geometry around world z-axis + + var m1 = new Matrix4(); + + return function rotateZ( angle ) { + + m1.makeRotationZ( angle ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + translate: function () { + + // translate geometry + + var m1 = new Matrix4(); + + return function translate( x, y, z ) { + + m1.makeTranslation( x, y, z ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + scale: function () { + + // scale geometry + + var m1 = new Matrix4(); + + return function scale( x, y, z ) { + + m1.makeScale( x, y, z ); + + this.applyMatrix( m1 ); + + return this; + + }; + + }(), + + lookAt: function () { + + var obj = new Object3D(); + + return function lookAt( vector ) { + + obj.lookAt( vector ); + + obj.updateMatrix(); + + this.applyMatrix( obj.matrix ); + + }; + + }(), + + center: function () { + + this.computeBoundingBox(); + + var offset = this.boundingBox.getCenter().negate(); + + this.translate( offset.x, offset.y, offset.z ); + + return offset; + + }, + + setFromObject: function ( object ) { + + // console.log( 'THREE.BufferGeometry.setFromObject(). Converting', object, this ); + + var geometry = object.geometry; + + if ( object.isPoints || object.isLine ) { + + var positions = new Float32BufferAttribute( geometry.vertices.length * 3, 3 ); + var colors = new Float32BufferAttribute( geometry.colors.length * 3, 3 ); + + this.addAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) ); + this.addAttribute( 'color', colors.copyColorsArray( geometry.colors ) ); + + if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) { + + var lineDistances = new Float32BufferAttribute( geometry.lineDistances.length, 1 ); + + this.addAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) ); + + } + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + } else if ( object.isMesh ) { + + if ( geometry && geometry.isGeometry ) { + + this.fromGeometry( geometry ); + + } + + } + + return this; + + }, + + setFromPoints: function ( points ) { + + var position = []; + + for ( var i = 0, l = points.length; i < l; i ++ ) { + + var point = points[ i ]; + position.push( point.x, point.y, point.z || 0 ); + + } + + this.addAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + + return this; + + }, + + updateFromObject: function ( object ) { + + var geometry = object.geometry; + + if ( object.isMesh ) { + + var direct = geometry.__directGeometry; + + if ( geometry.elementsNeedUpdate === true ) { + + direct = undefined; + geometry.elementsNeedUpdate = false; + + } + + if ( direct === undefined ) { + + return this.fromGeometry( geometry ); + + } + + direct.verticesNeedUpdate = geometry.verticesNeedUpdate; + direct.normalsNeedUpdate = geometry.normalsNeedUpdate; + direct.colorsNeedUpdate = geometry.colorsNeedUpdate; + direct.uvsNeedUpdate = geometry.uvsNeedUpdate; + direct.groupsNeedUpdate = geometry.groupsNeedUpdate; + + geometry.verticesNeedUpdate = false; + geometry.normalsNeedUpdate = false; + geometry.colorsNeedUpdate = false; + geometry.uvsNeedUpdate = false; + geometry.groupsNeedUpdate = false; + + geometry = direct; + + } + + var attribute; + + if ( geometry.verticesNeedUpdate === true ) { + + attribute = this.attributes.position; + + if ( attribute !== undefined ) { + + attribute.copyVector3sArray( geometry.vertices ); + attribute.needsUpdate = true; + + } + + geometry.verticesNeedUpdate = false; + + } + + if ( geometry.normalsNeedUpdate === true ) { + + attribute = this.attributes.normal; + + if ( attribute !== undefined ) { + + attribute.copyVector3sArray( geometry.normals ); + attribute.needsUpdate = true; + + } + + geometry.normalsNeedUpdate = false; + + } + + if ( geometry.colorsNeedUpdate === true ) { + + attribute = this.attributes.color; + + if ( attribute !== undefined ) { + + attribute.copyColorsArray( geometry.colors ); + attribute.needsUpdate = true; + + } + + geometry.colorsNeedUpdate = false; + + } + + if ( geometry.uvsNeedUpdate ) { + + attribute = this.attributes.uv; + + if ( attribute !== undefined ) { + + attribute.copyVector2sArray( geometry.uvs ); + attribute.needsUpdate = true; + + } + + geometry.uvsNeedUpdate = false; + + } + + if ( geometry.lineDistancesNeedUpdate ) { + + attribute = this.attributes.lineDistance; + + if ( attribute !== undefined ) { + + attribute.copyArray( geometry.lineDistances ); + attribute.needsUpdate = true; + + } + + geometry.lineDistancesNeedUpdate = false; + + } + + if ( geometry.groupsNeedUpdate ) { + + geometry.computeGroups( object.geometry ); + this.groups = geometry.groups; + + geometry.groupsNeedUpdate = false; + + } + + return this; + + }, + + fromGeometry: function ( geometry ) { + + geometry.__directGeometry = new DirectGeometry().fromGeometry( geometry ); + + return this.fromDirectGeometry( geometry.__directGeometry ); + + }, + + fromDirectGeometry: function ( geometry ) { + + var positions = new Float32Array( geometry.vertices.length * 3 ); + this.addAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); + + if ( geometry.normals.length > 0 ) { + + var normals = new Float32Array( geometry.normals.length * 3 ); + this.addAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); + + } + + if ( geometry.colors.length > 0 ) { + + var colors = new Float32Array( geometry.colors.length * 3 ); + this.addAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); + + } + + if ( geometry.uvs.length > 0 ) { + + var uvs = new Float32Array( geometry.uvs.length * 2 ); + this.addAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); + + } + + if ( geometry.uvs2.length > 0 ) { + + var uvs2 = new Float32Array( geometry.uvs2.length * 2 ); + this.addAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); + + } + + // groups + + this.groups = geometry.groups; + + // morphs + + for ( var name in geometry.morphTargets ) { + + var array = []; + var morphTargets = geometry.morphTargets[ name ]; + + for ( var i = 0, l = morphTargets.length; i < l; i ++ ) { + + var morphTarget = morphTargets[ i ]; + + var attribute = new Float32BufferAttribute( morphTarget.length * 3, 3 ); + + array.push( attribute.copyVector3sArray( morphTarget ) ); + + } + + this.morphAttributes[ name ] = array; + + } + + // skinning + + if ( geometry.skinIndices.length > 0 ) { + + var skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 ); + this.addAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); + + } + + if ( geometry.skinWeights.length > 0 ) { + + var skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 ); + this.addAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); + + } + + // + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + return this; + + }, + + computeBoundingBox: function () { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + var position = this.attributes.position; + + if ( position !== undefined ) { + + this.boundingBox.setFromBufferAttribute( position ); + + } else { + + this.boundingBox.makeEmpty(); + + } + + if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingBox: Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this ); + + } + + }, + + computeBoundingSphere: function () { + + var box = new Box3(); + var vector = new Vector3(); + + return function computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + var position = this.attributes.position; + + if ( position ) { + + var center = this.boundingSphere.center; + + box.setFromBufferAttribute( position ); + box.getCenter( center ); + + // hoping to find a boundingSphere with a radius smaller than the + // boundingSphere of the boundingBox: sqrt(3) smaller in the best case + + var maxRadiusSq = 0; + + for ( var i = 0, il = position.count; i < il; i ++ ) { + + vector.x = position.getX( i ); + vector.y = position.getY( i ); + vector.z = position.getZ( i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this ); + + } + + } + + }; + + }(), + + computeFaceNormals: function () { + + // backwards compatibility + + }, + + computeVertexNormals: function () { + + var index = this.index; + var attributes = this.attributes; + var groups = this.groups; + + if ( attributes.position ) { + + var positions = attributes.position.array; + + if ( attributes.normal === undefined ) { + + this.addAttribute( 'normal', new BufferAttribute( new Float32Array( positions.length ), 3 ) ); + + } else { + + // reset existing normals to zero + + var array = attributes.normal.array; + + for ( var i = 0, il = array.length; i < il; i ++ ) { + + array[ i ] = 0; + + } + + } + + var normals = attributes.normal.array; + + var vA, vB, vC; + var pA = new Vector3(), pB = new Vector3(), pC = new Vector3(); + var cb = new Vector3(), ab = new Vector3(); + + // indexed elements + + if ( index ) { + + var indices = index.array; + + if ( groups.length === 0 ) { + + this.addGroup( 0, indices.length ); + + } + + for ( var j = 0, jl = groups.length; j < jl; ++ j ) { + + var group = groups[ j ]; + + var start = group.start; + var count = group.count; + + for ( var i = start, il = start + count; i < il; i += 3 ) { + + vA = indices[ i + 0 ] * 3; + vB = indices[ i + 1 ] * 3; + vC = indices[ i + 2 ] * 3; + + pA.fromArray( positions, vA ); + pB.fromArray( positions, vB ); + pC.fromArray( positions, vC ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ vA ] += cb.x; + normals[ vA + 1 ] += cb.y; + normals[ vA + 2 ] += cb.z; + + normals[ vB ] += cb.x; + normals[ vB + 1 ] += cb.y; + normals[ vB + 2 ] += cb.z; + + normals[ vC ] += cb.x; + normals[ vC + 1 ] += cb.y; + normals[ vC + 2 ] += cb.z; + + } + + } + + } else { + + // non-indexed elements (unconnected triangle soup) + + for ( var i = 0, il = positions.length; i < il; i += 9 ) { + + pA.fromArray( positions, i ); + pB.fromArray( positions, i + 3 ); + pC.fromArray( positions, i + 6 ); + + cb.subVectors( pC, pB ); + ab.subVectors( pA, pB ); + cb.cross( ab ); + + normals[ i ] = cb.x; + normals[ i + 1 ] = cb.y; + normals[ i + 2 ] = cb.z; + + normals[ i + 3 ] = cb.x; + normals[ i + 4 ] = cb.y; + normals[ i + 5 ] = cb.z; + + normals[ i + 6 ] = cb.x; + normals[ i + 7 ] = cb.y; + normals[ i + 8 ] = cb.z; + + } + + } + + this.normalizeNormals(); + + attributes.normal.needsUpdate = true; + + } + + }, + + merge: function ( geometry, offset ) { + + if ( ! ( geometry && geometry.isBufferGeometry ) ) { + + console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry ); + return; + + } + + if ( offset === undefined ) offset = 0; + + var attributes = this.attributes; + + for ( var key in attributes ) { + + if ( geometry.attributes[ key ] === undefined ) continue; + + var attribute1 = attributes[ key ]; + var attributeArray1 = attribute1.array; + + var attribute2 = geometry.attributes[ key ]; + var attributeArray2 = attribute2.array; + + var attributeSize = attribute2.itemSize; + + for ( var i = 0, j = attributeSize * offset; i < attributeArray2.length; i ++, j ++ ) { + + attributeArray1[ j ] = attributeArray2[ i ]; + + } + + } + + return this; + + }, + + normalizeNormals: function () { + + var vector = new Vector3(); + + return function normalizeNormals() { + + var normals = this.attributes.normal; + + for ( var i = 0, il = normals.count; i < il; i ++ ) { + + vector.x = normals.getX( i ); + vector.y = normals.getY( i ); + vector.z = normals.getZ( i ); + + vector.normalize(); + + normals.setXYZ( i, vector.x, vector.y, vector.z ); + + } + + }; + + }(), + + toNonIndexed: function () { + + if ( this.index === null ) { + + console.warn( 'THREE.BufferGeometry.toNonIndexed(): Geometry is already non-indexed.' ); + return this; + + } + + var geometry2 = new BufferGeometry(); + + var indices = this.index.array; + var attributes = this.attributes; + + for ( var name in attributes ) { + + var attribute = attributes[ name ]; + + var array = attribute.array; + var itemSize = attribute.itemSize; + + var array2 = new array.constructor( indices.length * itemSize ); + + var index = 0, index2 = 0; + + for ( var i = 0, l = indices.length; i < l; i ++ ) { + + index = indices[ i ] * itemSize; + + for ( var j = 0; j < itemSize; j ++ ) { + + array2[ index2 ++ ] = array[ index ++ ]; + + } + + } + + geometry2.addAttribute( name, new BufferAttribute( array2, itemSize ) ); + + } + + return geometry2; + + }, + + toJSON: function () { + + var data = { + metadata: { + version: 4.5, + type: 'BufferGeometry', + generator: 'BufferGeometry.toJSON' + } + }; + + // standard BufferGeometry serialization + + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + + if ( this.parameters !== undefined ) { + + var parameters = this.parameters; + + for ( var key in parameters ) { + + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + + } + + return data; + + } + + data.data = { attributes: {} }; + + var index = this.index; + + if ( index !== null ) { + + var array = Array.prototype.slice.call( index.array ); + + data.data.index = { + type: index.array.constructor.name, + array: array + }; + + } + + var attributes = this.attributes; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + + var array = Array.prototype.slice.call( attribute.array ); + + data.data.attributes[ key ] = { + itemSize: attribute.itemSize, + type: attribute.array.constructor.name, + array: array, + normalized: attribute.normalized + }; + + } + + var groups = this.groups; + + if ( groups.length > 0 ) { + + data.data.groups = JSON.parse( JSON.stringify( groups ) ); + + } + + var boundingSphere = this.boundingSphere; + + if ( boundingSphere !== null ) { + + data.data.boundingSphere = { + center: boundingSphere.center.toArray(), + radius: boundingSphere.radius + }; + + } + + return data; + + }, + + clone: function () { + + /* + // Handle primitives + + var parameters = this.parameters; + + if ( parameters !== undefined ) { + + var values = []; + + for ( var key in parameters ) { + + values.push( parameters[ key ] ); + + } + + var geometry = Object.create( this.constructor.prototype ); + this.constructor.apply( geometry, values ); + return geometry; + + } + + return new this.constructor().copy( this ); + */ + + return new BufferGeometry().copy( this ); + + }, + + copy: function ( source ) { + + var name, i, l; + + // reset + + this.index = null; + this.attributes = {}; + this.morphAttributes = {}; + this.groups = []; + this.boundingBox = null; + this.boundingSphere = null; + + // name + + this.name = source.name; + + // index + + var index = source.index; + + if ( index !== null ) { + + this.setIndex( index.clone() ); + + } + + // attributes + + var attributes = source.attributes; + + for ( name in attributes ) { + + var attribute = attributes[ name ]; + this.addAttribute( name, attribute.clone() ); + + } + + // morph attributes + + var morphAttributes = source.morphAttributes; + + for ( name in morphAttributes ) { + + var array = []; + var morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes + + for ( i = 0, l = morphAttribute.length; i < l; i ++ ) { + + array.push( morphAttribute[ i ].clone() ); + + } + + this.morphAttributes[ name ] = array; + + } + + // groups + + var groups = source.groups; + + for ( i = 0, l = groups.length; i < l; i ++ ) { + + var group = groups[ i ]; + this.addGroup( group.start, group.count, group.materialIndex ); + + } + + // bounding box + + var boundingBox = source.boundingBox; + + if ( boundingBox !== null ) { + + this.boundingBox = boundingBox.clone(); + + } + + // bounding sphere + + var boundingSphere = source.boundingSphere; + + if ( boundingSphere !== null ) { + + this.boundingSphere = boundingSphere.clone(); + + } + + // draw range + + this.drawRange.start = source.drawRange.start; + this.drawRange.count = source.drawRange.count; + + return this; + + }, + + dispose: function () { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + // BoxGeometry + + function BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) { + + Geometry.call( this ); + + this.type = 'BoxGeometry'; + + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; + + this.fromBufferGeometry( new BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) ); + this.mergeVertices(); + + } + + BoxGeometry.prototype = Object.create( Geometry.prototype ); + BoxGeometry.prototype.constructor = BoxGeometry; + + // BoxBufferGeometry + + function BoxBufferGeometry( width, height, depth, widthSegments, heightSegments, depthSegments ) { + + BufferGeometry.call( this ); + + this.type = 'BoxBufferGeometry'; + + this.parameters = { + width: width, + height: height, + depth: depth, + widthSegments: widthSegments, + heightSegments: heightSegments, + depthSegments: depthSegments + }; + + var scope = this; + + width = width || 1; + height = height || 1; + depth = depth || 1; + + // segments + + widthSegments = Math.floor( widthSegments ) || 1; + heightSegments = Math.floor( heightSegments ) || 1; + depthSegments = Math.floor( depthSegments ) || 1; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var numberOfVertices = 0; + var groupStart = 0; + + // build each side of the box geometry + + buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px + buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx + buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py + buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny + buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz + buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) { + + var segmentWidth = width / gridX; + var segmentHeight = height / gridY; + + var widthHalf = width / 2; + var heightHalf = height / 2; + var depthHalf = depth / 2; + + var gridX1 = gridX + 1; + var gridY1 = gridY + 1; + + var vertexCounter = 0; + var groupCount = 0; + + var ix, iy; + + var vector = new Vector3(); + + // generate vertices, normals and uvs + + for ( iy = 0; iy < gridY1; iy ++ ) { + + var y = iy * segmentHeight - heightHalf; + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var x = ix * segmentWidth - widthHalf; + + // set values to correct vector component + + vector[ u ] = x * udir; + vector[ v ] = y * vdir; + vector[ w ] = depthHalf; + + // now apply vector to vertex buffer + + vertices.push( vector.x, vector.y, vector.z ); + + // set values to correct vector component + + vector[ u ] = 0; + vector[ v ] = 0; + vector[ w ] = depth > 0 ? 1 : - 1; + + // now apply vector to normal buffer + + normals.push( vector.x, vector.y, vector.z ); + + // uvs + + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); + + // counters + + vertexCounter += 1; + + } + + } + + // indices + + // 1. you need three indices to draw a single face + // 2. a single segment consists of two faces + // 3. so we need to generate six (2*3) indices per segment + + for ( iy = 0; iy < gridY; iy ++ ) { + + for ( ix = 0; ix < gridX; ix ++ ) { + + var a = numberOfVertices + ix + gridX1 * iy; + var b = numberOfVertices + ix + gridX1 * ( iy + 1 ); + var c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = numberOfVertices + ( ix + 1 ) + gridX1 * iy; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + // increase counter + + groupCount += 6; + + } + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, materialIndex ); + + // calculate new start value for groups + + groupStart += groupCount; + + // update total number of vertices + + numberOfVertices += vertexCounter; + + } + + } + + BoxBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + BoxBufferGeometry.prototype.constructor = BoxBufferGeometry; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + // PlaneGeometry + + function PlaneGeometry( width, height, widthSegments, heightSegments ) { + + Geometry.call( this ); + + this.type = 'PlaneGeometry'; + + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; + + this.fromBufferGeometry( new PlaneBufferGeometry( width, height, widthSegments, heightSegments ) ); + this.mergeVertices(); + + } + + PlaneGeometry.prototype = Object.create( Geometry.prototype ); + PlaneGeometry.prototype.constructor = PlaneGeometry; + + // PlaneBufferGeometry + + function PlaneBufferGeometry( width, height, widthSegments, heightSegments ) { + + BufferGeometry.call( this ); + + this.type = 'PlaneBufferGeometry'; + + this.parameters = { + width: width, + height: height, + widthSegments: widthSegments, + heightSegments: heightSegments + }; + + width = width || 1; + height = height || 1; + + var width_half = width / 2; + var height_half = height / 2; + + var gridX = Math.floor( widthSegments ) || 1; + var gridY = Math.floor( heightSegments ) || 1; + + var gridX1 = gridX + 1; + var gridY1 = gridY + 1; + + var segment_width = width / gridX; + var segment_height = height / gridY; + + var ix, iy; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // generate vertices, normals and uvs + + for ( iy = 0; iy < gridY1; iy ++ ) { + + var y = iy * segment_height - height_half; + + for ( ix = 0; ix < gridX1; ix ++ ) { + + var x = ix * segment_width - width_half; + + vertices.push( x, - y, 0 ); + + normals.push( 0, 0, 1 ); + + uvs.push( ix / gridX ); + uvs.push( 1 - ( iy / gridY ) ); + + } + + } + + // indices + + for ( iy = 0; iy < gridY; iy ++ ) { + + for ( ix = 0; ix < gridX; ix ++ ) { + + var a = ix + gridX1 * iy; + var b = ix + gridX1 * ( iy + 1 ); + var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); + var d = ( ix + 1 ) + gridX1 * iy; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + PlaneBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + PlaneBufferGeometry.prototype.constructor = PlaneBufferGeometry; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * lightMapIntensity: + * + * aoMap: new THREE.Texture( ), + * aoMapIntensity: + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * depthTest: , + * depthWrite: , + * + * wireframe: , + * wireframeLinewidth: , + * + * skinning: , + * morphTargets: + * } + */ + + function MeshBasicMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshBasicMaterial'; + + this.color = new Color( 0xffffff ); // emissive + + this.map = null; + + this.lightMap = null; + this.lightMapIntensity = 1.0; + + this.aoMap = null; + this.aoMapIntensity = 1.0; + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.skinning = false; + this.morphTargets = false; + + this.lights = false; + + this.setValues( parameters ); + + } + + MeshBasicMaterial.prototype = Object.create( Material.prototype ); + MeshBasicMaterial.prototype.constructor = MeshBasicMaterial; + + MeshBasicMaterial.prototype.isMeshBasicMaterial = true; + + MeshBasicMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + + return this; + + }; + + /** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * defines: { "label" : "value" }, + * uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } }, + * + * fragmentShader: , + * vertexShader: , + * + * wireframe: , + * wireframeLinewidth: , + * + * lights: , + * + * skinning: , + * morphTargets: , + * morphNormals: + * } + */ + + function ShaderMaterial( parameters ) { + + Material.call( this ); + + this.type = 'ShaderMaterial'; + + this.defines = {}; + this.uniforms = {}; + + this.vertexShader = 'void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}'; + this.fragmentShader = 'void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}'; + + this.linewidth = 1; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.fog = false; // set to use scene fog + this.lights = false; // set to use scene lights + this.clipping = false; // set to use user-defined clipping planes + + this.skinning = false; // set to use skinning attribute streams + this.morphTargets = false; // set to use morph targets + this.morphNormals = false; // set to use morph normals + + this.extensions = { + derivatives: false, // set to use derivatives + fragDepth: false, // set to use fragment depth values + drawBuffers: false, // set to use draw buffers + shaderTextureLOD: false // set to use shader texture LOD + }; + + // When rendered geometry doesn't include these attributes but the material does, + // use these default values in WebGL. This avoids errors when buffer data is missing. + this.defaultAttributeValues = { + 'color': [ 1, 1, 1 ], + 'uv': [ 0, 0 ], + 'uv2': [ 0, 0 ] + }; + + this.index0AttributeName = undefined; + this.uniformsNeedUpdate = false; + + if ( parameters !== undefined ) { + + if ( parameters.attributes !== undefined ) { + + console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' ); + + } + + this.setValues( parameters ); + + } + + } + + ShaderMaterial.prototype = Object.create( Material.prototype ); + ShaderMaterial.prototype.constructor = ShaderMaterial; + + ShaderMaterial.prototype.isShaderMaterial = true; + + ShaderMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.fragmentShader = source.fragmentShader; + this.vertexShader = source.vertexShader; + + this.uniforms = UniformsUtils.clone( source.uniforms ); + + this.defines = source.defines; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + this.lights = source.lights; + this.clipping = source.clipping; + + this.skinning = source.skinning; + + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; + + this.extensions = source.extensions; + + return this; + + }; + + ShaderMaterial.prototype.toJSON = function ( meta ) { + + var data = Material.prototype.toJSON.call( this, meta ); + + data.uniforms = this.uniforms; + data.vertexShader = this.vertexShader; + data.fragmentShader = this.fragmentShader; + + return data; + + }; + + /** + * @author bhouston / http://clara.io + */ + + function Ray( origin, direction ) { + + this.origin = ( origin !== undefined ) ? origin : new Vector3(); + this.direction = ( direction !== undefined ) ? direction : new Vector3(); + + } + + Object.assign( Ray.prototype, { + + set: function ( origin, direction ) { + + this.origin.copy( origin ); + this.direction.copy( direction ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( ray ) { + + this.origin.copy( ray.origin ); + this.direction.copy( ray.direction ); + + return this; + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + return result.copy( this.direction ).multiplyScalar( t ).add( this.origin ); + + }, + + lookAt: function ( v ) { + + this.direction.copy( v ).sub( this.origin ).normalize(); + + return this; + + }, + + recast: function () { + + var v1 = new Vector3(); + + return function recast( t ) { + + this.origin.copy( this.at( t, v1 ) ); + + return this; + + }; + + }(), + + closestPointToPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + result.subVectors( point, this.origin ); + var directionDistance = result.dot( this.direction ); + + if ( directionDistance < 0 ) { + + return result.copy( this.origin ); + + } + + return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + }, + + distanceToPoint: function ( point ) { + + return Math.sqrt( this.distanceSqToPoint( point ) ); + + }, + + distanceSqToPoint: function () { + + var v1 = new Vector3(); + + return function distanceSqToPoint( point ) { + + var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction ); + + // point behind the ray + + if ( directionDistance < 0 ) { + + return this.origin.distanceToSquared( point ); + + } + + v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin ); + + return v1.distanceToSquared( point ); + + }; + + }(), + + distanceSqToSegment: function () { + + var segCenter = new Vector3(); + var segDir = new Vector3(); + var diff = new Vector3(); + + return function distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) { + + // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h + // It returns the min distance between the ray and the segment + // defined by v0 and v1 + // It can also set two optional targets : + // - The closest point on the ray + // - The closest point on the segment + + segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 ); + segDir.copy( v1 ).sub( v0 ).normalize(); + diff.copy( this.origin ).sub( segCenter ); + + var segExtent = v0.distanceTo( v1 ) * 0.5; + var a01 = - this.direction.dot( segDir ); + var b0 = diff.dot( this.direction ); + var b1 = - diff.dot( segDir ); + var c = diff.lengthSq(); + var det = Math.abs( 1 - a01 * a01 ); + var s0, s1, sqrDist, extDet; + + if ( det > 0 ) { + + // The ray and segment are not parallel. + + s0 = a01 * b1 - b0; + s1 = a01 * b0 - b1; + extDet = segExtent * det; + + if ( s0 >= 0 ) { + + if ( s1 >= - extDet ) { + + if ( s1 <= extDet ) { + + // region 0 + // Minimum at interior points of ray and segment. + + var invDet = 1 / det; + s0 *= invDet; + s1 *= invDet; + sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c; + + } else { + + // region 1 + + s1 = segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + // region 5 + + s1 = - segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } else { + + if ( s1 <= - extDet ) { + + // region 4 + + s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } else if ( s1 <= extDet ) { + + // region 3 + + s0 = 0; + s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = s1 * ( s1 + 2 * b1 ) + c; + + } else { + + // region 2 + + s0 = Math.max( 0, - ( a01 * segExtent + b0 ) ); + s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + } + + } else { + + // Ray and segment are parallel. + + s1 = ( a01 > 0 ) ? - segExtent : segExtent; + s0 = Math.max( 0, - ( a01 * s1 + b0 ) ); + sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c; + + } + + if ( optionalPointOnRay ) { + + optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin ); + + } + + if ( optionalPointOnSegment ) { + + optionalPointOnSegment.copy( segDir ).multiplyScalar( s1 ).add( segCenter ); + + } + + return sqrDist; + + }; + + }(), + + intersectSphere: function () { + + var v1 = new Vector3(); + + return function intersectSphere( sphere, optionalTarget ) { + + v1.subVectors( sphere.center, this.origin ); + var tca = v1.dot( this.direction ); + var d2 = v1.dot( v1 ) - tca * tca; + var radius2 = sphere.radius * sphere.radius; + + if ( d2 > radius2 ) return null; + + var thc = Math.sqrt( radius2 - d2 ); + + // t0 = first intersect point - entrance on front of sphere + var t0 = tca - thc; + + // t1 = second intersect point - exit point on back of sphere + var t1 = tca + thc; + + // test to see if both t0 and t1 are behind the ray - if so, return null + if ( t0 < 0 && t1 < 0 ) return null; + + // test to see if t0 is behind the ray: + // if it is, the ray is inside the sphere, so return the second exit point scaled by t1, + // in order to always return an intersect point that is in front of the ray. + if ( t0 < 0 ) return this.at( t1, optionalTarget ); + + // else t0 is in front of the ray, so return the first collision point scaled by t0 + return this.at( t0, optionalTarget ); + + }; + + }(), + + intersectsSphere: function ( sphere ) { + + return this.distanceToPoint( sphere.center ) <= sphere.radius; + + }, + + distanceToPlane: function ( plane ) { + + var denominator = plane.normal.dot( this.direction ); + + if ( denominator === 0 ) { + + // line is coplanar, return origin + if ( plane.distanceToPoint( this.origin ) === 0 ) { + + return 0; + + } + + // Null is preferable to undefined since undefined means.... it is undefined + + return null; + + } + + var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator; + + // Return if the ray never intersects the plane + + return t >= 0 ? t : null; + + }, + + intersectPlane: function ( plane, optionalTarget ) { + + var t = this.distanceToPlane( plane ); + + if ( t === null ) { + + return null; + + } + + return this.at( t, optionalTarget ); + + }, + + intersectsPlane: function ( plane ) { + + // check if the ray lies on the plane first + + var distToPoint = plane.distanceToPoint( this.origin ); + + if ( distToPoint === 0 ) { + + return true; + + } + + var denominator = plane.normal.dot( this.direction ); + + if ( denominator * distToPoint < 0 ) { + + return true; + + } + + // ray origin is behind the plane (and is pointing behind it) + + return false; + + }, + + intersectBox: function ( box, optionalTarget ) { + + var tmin, tmax, tymin, tymax, tzmin, tzmax; + + var invdirx = 1 / this.direction.x, + invdiry = 1 / this.direction.y, + invdirz = 1 / this.direction.z; + + var origin = this.origin; + + if ( invdirx >= 0 ) { + + tmin = ( box.min.x - origin.x ) * invdirx; + tmax = ( box.max.x - origin.x ) * invdirx; + + } else { + + tmin = ( box.max.x - origin.x ) * invdirx; + tmax = ( box.min.x - origin.x ) * invdirx; + + } + + if ( invdiry >= 0 ) { + + tymin = ( box.min.y - origin.y ) * invdiry; + tymax = ( box.max.y - origin.y ) * invdiry; + + } else { + + tymin = ( box.max.y - origin.y ) * invdiry; + tymax = ( box.min.y - origin.y ) * invdiry; + + } + + if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null; + + // These lines also handle the case where tmin or tmax is NaN + // (result of 0 * Infinity). x !== x returns true if x is NaN + + if ( tymin > tmin || tmin !== tmin ) tmin = tymin; + + if ( tymax < tmax || tmax !== tmax ) tmax = tymax; + + if ( invdirz >= 0 ) { + + tzmin = ( box.min.z - origin.z ) * invdirz; + tzmax = ( box.max.z - origin.z ) * invdirz; + + } else { + + tzmin = ( box.max.z - origin.z ) * invdirz; + tzmax = ( box.min.z - origin.z ) * invdirz; + + } + + if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null; + + if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin; + + if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax; + + //return point closest to the ray (positive side) + + if ( tmax < 0 ) return null; + + return this.at( tmin >= 0 ? tmin : tmax, optionalTarget ); + + }, + + intersectsBox: ( function () { + + var v = new Vector3(); + + return function intersectsBox( box ) { + + return this.intersectBox( box, v ) !== null; + + }; + + } )(), + + intersectTriangle: function () { + + // Compute the offset origin, edges, and normal. + var diff = new Vector3(); + var edge1 = new Vector3(); + var edge2 = new Vector3(); + var normal = new Vector3(); + + return function intersectTriangle( a, b, c, backfaceCulling, optionalTarget ) { + + // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h + + edge1.subVectors( b, a ); + edge2.subVectors( c, a ); + normal.crossVectors( edge1, edge2 ); + + // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction, + // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by + // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2)) + // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q)) + // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N) + var DdN = this.direction.dot( normal ); + var sign; + + if ( DdN > 0 ) { + + if ( backfaceCulling ) return null; + sign = 1; + + } else if ( DdN < 0 ) { + + sign = - 1; + DdN = - DdN; + + } else { + + return null; + + } + + diff.subVectors( this.origin, a ); + var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) ); + + // b1 < 0, no intersection + if ( DdQxE2 < 0 ) { + + return null; + + } + + var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) ); + + // b2 < 0, no intersection + if ( DdE1xQ < 0 ) { + + return null; + + } + + // b1+b2 > 1, no intersection + if ( DdQxE2 + DdE1xQ > DdN ) { + + return null; + + } + + // Line intersects triangle, check if ray does. + var QdN = - sign * diff.dot( normal ); + + // t < 0, no intersection + if ( QdN < 0 ) { + + return null; + + } + + // Ray intersects triangle. + return this.at( QdN / DdN, optionalTarget ); + + }; + + }(), + + applyMatrix4: function ( matrix4 ) { + + this.origin.applyMatrix4( matrix4 ); + this.direction.transformDirection( matrix4 ); + + return this; + + }, + + equals: function ( ray ) { + + return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction ); + + } + + } ); + + /** + * @author bhouston / http://clara.io + */ + + function Line3( start, end ) { + + this.start = ( start !== undefined ) ? start : new Vector3(); + this.end = ( end !== undefined ) ? end : new Vector3(); + + } + + Object.assign( Line3.prototype, { + + set: function ( start, end ) { + + this.start.copy( start ); + this.end.copy( end ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( line ) { + + this.start.copy( line.start ); + this.end.copy( line.end ); + + return this; + + }, + + getCenter: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 ); + + }, + + delta: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return result.subVectors( this.end, this.start ); + + }, + + distanceSq: function () { + + return this.start.distanceToSquared( this.end ); + + }, + + distance: function () { + + return this.start.distanceTo( this.end ); + + }, + + at: function ( t, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + closestPointToPointParameter: function () { + + var startP = new Vector3(); + var startEnd = new Vector3(); + + return function closestPointToPointParameter( point, clampToLine ) { + + startP.subVectors( point, this.start ); + startEnd.subVectors( this.end, this.start ); + + var startEnd2 = startEnd.dot( startEnd ); + var startEnd_startP = startEnd.dot( startP ); + + var t = startEnd_startP / startEnd2; + + if ( clampToLine ) { + + t = _Math.clamp( t, 0, 1 ); + + } + + return t; + + }; + + }(), + + closestPointToPoint: function ( point, clampToLine, optionalTarget ) { + + var t = this.closestPointToPointParameter( point, clampToLine ); + + var result = optionalTarget || new Vector3(); + + return this.delta( result ).multiplyScalar( t ).add( this.start ); + + }, + + applyMatrix4: function ( matrix ) { + + this.start.applyMatrix4( matrix ); + this.end.applyMatrix4( matrix ); + + return this; + + }, + + equals: function ( line ) { + + return line.start.equals( this.start ) && line.end.equals( this.end ); + + } + + } ); + + /** + * @author bhouston / http://clara.io + * @author mrdoob / http://mrdoob.com/ + */ + + function Triangle( a, b, c ) { + + this.a = ( a !== undefined ) ? a : new Vector3(); + this.b = ( b !== undefined ) ? b : new Vector3(); + this.c = ( c !== undefined ) ? c : new Vector3(); + + } + + Object.assign( Triangle, { + + normal: function () { + + var v0 = new Vector3(); + + return function normal( a, b, c, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + + result.subVectors( c, b ); + v0.subVectors( a, b ); + result.cross( v0 ); + + var resultLengthSq = result.lengthSq(); + if ( resultLengthSq > 0 ) { + + return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) ); + + } + + return result.set( 0, 0, 0 ); + + }; + + }(), + + // static/instance method to calculate barycentric coordinates + // based on: http://www.blackpawn.com/texts/pointinpoly/default.html + barycoordFromPoint: function () { + + var v0 = new Vector3(); + var v1 = new Vector3(); + var v2 = new Vector3(); + + return function barycoordFromPoint( point, a, b, c, optionalTarget ) { + + v0.subVectors( c, a ); + v1.subVectors( b, a ); + v2.subVectors( point, a ); + + var dot00 = v0.dot( v0 ); + var dot01 = v0.dot( v1 ); + var dot02 = v0.dot( v2 ); + var dot11 = v1.dot( v1 ); + var dot12 = v1.dot( v2 ); + + var denom = ( dot00 * dot11 - dot01 * dot01 ); + + var result = optionalTarget || new Vector3(); + + // collinear or singular triangle + if ( denom === 0 ) { + + // arbitrary location outside of triangle? + // not sure if this is the best idea, maybe should be returning undefined + return result.set( - 2, - 1, - 1 ); + + } + + var invDenom = 1 / denom; + var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom; + var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom; + + // barycentric coordinates must always sum to 1 + return result.set( 1 - u - v, v, u ); + + }; + + }(), + + containsPoint: function () { + + var v1 = new Vector3(); + + return function containsPoint( point, a, b, c ) { + + var result = Triangle.barycoordFromPoint( point, a, b, c, v1 ); + + return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 ); + + }; + + }() + + } ); + + Object.assign( Triangle.prototype, { + + set: function ( a, b, c ) { + + this.a.copy( a ); + this.b.copy( b ); + this.c.copy( c ); + + return this; + + }, + + setFromPointsAndIndices: function ( points, i0, i1, i2 ) { + + this.a.copy( points[ i0 ] ); + this.b.copy( points[ i1 ] ); + this.c.copy( points[ i2 ] ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( triangle ) { + + this.a.copy( triangle.a ); + this.b.copy( triangle.b ); + this.c.copy( triangle.c ); + + return this; + + }, + + area: function () { + + var v0 = new Vector3(); + var v1 = new Vector3(); + + return function area() { + + v0.subVectors( this.c, this.b ); + v1.subVectors( this.a, this.b ); + + return v0.cross( v1 ).length() * 0.5; + + }; + + }(), + + midpoint: function ( optionalTarget ) { + + var result = optionalTarget || new Vector3(); + return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 ); + + }, + + normal: function ( optionalTarget ) { + + return Triangle.normal( this.a, this.b, this.c, optionalTarget ); + + }, + + plane: function ( optionalTarget ) { + + var result = optionalTarget || new Plane(); + + return result.setFromCoplanarPoints( this.a, this.b, this.c ); + + }, + + barycoordFromPoint: function ( point, optionalTarget ) { + + return Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget ); + + }, + + containsPoint: function ( point ) { + + return Triangle.containsPoint( point, this.a, this.b, this.c ); + + }, + + intersectsBox: function ( box ) { + + return box.intersectsTriangle( this ); + + }, + + closestPointToPoint: function () { + + var plane = new Plane(); + var edgeList = [ new Line3(), new Line3(), new Line3() ]; + var projectedPoint = new Vector3(); + var closestPoint = new Vector3(); + + return function closestPointToPoint( point, optionalTarget ) { + + var result = optionalTarget || new Vector3(); + var minDistance = Infinity; + + // project the point onto the plane of the triangle + + plane.setFromCoplanarPoints( this.a, this.b, this.c ); + plane.projectPoint( point, projectedPoint ); + + // check if the projection lies within the triangle + + if ( this.containsPoint( projectedPoint ) === true ) { + + // if so, this is the closest point + + result.copy( projectedPoint ); + + } else { + + // if not, the point falls outside the triangle. the result is the closest point to the triangle's edges or vertices + + edgeList[ 0 ].set( this.a, this.b ); + edgeList[ 1 ].set( this.b, this.c ); + edgeList[ 2 ].set( this.c, this.a ); + + for ( var i = 0; i < edgeList.length; i ++ ) { + + edgeList[ i ].closestPointToPoint( projectedPoint, true, closestPoint ); + + var distance = projectedPoint.distanceToSquared( closestPoint ); + + if ( distance < minDistance ) { + + minDistance = distance; + + result.copy( closestPoint ); + + } + + } + + } + + return result; + + }; + + }(), + + equals: function ( triangle ) { + + return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author mikael emtinger / http://gomo.se/ + * @author jonobr1 / http://jonobr1.com/ + */ + + function Mesh( geometry, material ) { + + Object3D.call( this ); + + this.type = 'Mesh'; + + this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); + this.material = material !== undefined ? material : new MeshBasicMaterial( { color: Math.random() * 0xffffff } ); + + this.drawMode = TrianglesDrawMode; + + this.updateMorphTargets(); + + } + + Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Mesh, + + isMesh: true, + + setDrawMode: function ( value ) { + + this.drawMode = value; + + }, + + copy: function ( source ) { + + Object3D.prototype.copy.call( this, source ); + + this.drawMode = source.drawMode; + + if ( source.morphTargetInfluences !== undefined ) { + + this.morphTargetInfluences = source.morphTargetInfluences.slice(); + + } + + if ( source.morphTargetDictionary !== undefined ) { + + this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary ); + + } + + return this; + + }, + + updateMorphTargets: function () { + + var geometry = this.geometry; + var m, ml, name; + + if ( geometry.isBufferGeometry ) { + + var morphAttributes = geometry.morphAttributes; + var keys = Object.keys( morphAttributes ); + + if ( keys.length > 0 ) { + + var morphAttribute = morphAttributes[ keys[ 0 ] ]; + + if ( morphAttribute !== undefined ) { + + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( m = 0, ml = morphAttribute.length; m < ml; m ++ ) { + + name = morphAttribute[ m ].name || String( m ); + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; + + } + + } + + } + + } else { + + var morphTargets = geometry.morphTargets; + + if ( morphTargets !== undefined && morphTargets.length > 0 ) { + + this.morphTargetInfluences = []; + this.morphTargetDictionary = {}; + + for ( m = 0, ml = morphTargets.length; m < ml; m ++ ) { + + name = morphTargets[ m ].name || String( m ); + + this.morphTargetInfluences.push( 0 ); + this.morphTargetDictionary[ name ] = m; + + } + + } + + } + + }, + + raycast: ( function () { + + var inverseMatrix = new Matrix4(); + var ray = new Ray(); + var sphere = new Sphere(); + + var vA = new Vector3(); + var vB = new Vector3(); + var vC = new Vector3(); + + var tempA = new Vector3(); + var tempB = new Vector3(); + var tempC = new Vector3(); + + var uvA = new Vector2(); + var uvB = new Vector2(); + var uvC = new Vector2(); + + var barycoord = new Vector3(); + + var intersectionPoint = new Vector3(); + var intersectionPointWorld = new Vector3(); + + function uvIntersection( point, p1, p2, p3, uv1, uv2, uv3 ) { + + Triangle.barycoordFromPoint( point, p1, p2, p3, barycoord ); + + uv1.multiplyScalar( barycoord.x ); + uv2.multiplyScalar( barycoord.y ); + uv3.multiplyScalar( barycoord.z ); + + uv1.add( uv2 ).add( uv3 ); + + return uv1.clone(); + + } + + function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) { + + var intersect; + + if ( material.side === BackSide ) { + + intersect = ray.intersectTriangle( pC, pB, pA, true, point ); + + } else { + + intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point ); + + } + + if ( intersect === null ) return null; + + intersectionPointWorld.copy( point ); + intersectionPointWorld.applyMatrix4( object.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectionPointWorld ); + + if ( distance < raycaster.near || distance > raycaster.far ) return null; + + return { + distance: distance, + point: intersectionPointWorld.clone(), + object: object + }; + + } + + function checkBufferGeometryIntersection( object, raycaster, ray, position, uv, a, b, c ) { + + vA.fromBufferAttribute( position, a ); + vB.fromBufferAttribute( position, b ); + vC.fromBufferAttribute( position, c ); + + var intersection = checkIntersection( object, object.material, raycaster, ray, vA, vB, vC, intersectionPoint ); + + if ( intersection ) { + + if ( uv ) { + + uvA.fromBufferAttribute( uv, a ); + uvB.fromBufferAttribute( uv, b ); + uvC.fromBufferAttribute( uv, c ); + + intersection.uv = uvIntersection( intersectionPoint, vA, vB, vC, uvA, uvB, uvC ); + + } + + intersection.face = new Face3( a, b, c, Triangle.normal( vA, vB, vC ) ); + intersection.faceIndex = a; + + } + + return intersection; + + } + + return function raycast( raycaster, intersects ) { + + var geometry = this.geometry; + var material = this.material; + var matrixWorld = this.matrixWorld; + + if ( material === undefined ) return; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; + + // + + inverseMatrix.getInverse( matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + // Check boundingBox before continuing + + if ( geometry.boundingBox !== null ) { + + if ( ray.intersectsBox( geometry.boundingBox ) === false ) return; + + } + + var intersection; + + if ( geometry.isBufferGeometry ) { + + var a, b, c; + var index = geometry.index; + var position = geometry.attributes.position; + var uv = geometry.attributes.uv; + var i, l; + + if ( index !== null ) { + + // indexed buffer geometry + + for ( i = 0, l = index.count; i < l; i += 3 ) { + + a = index.getX( i ); + b = index.getX( i + 1 ); + c = index.getX( i + 2 ); + + intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c ); + + if ( intersection ) { + + intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indices buffer semantics + intersects.push( intersection ); + + } + + } + + } else if ( position !== undefined ) { + + // non-indexed buffer geometry + + for ( i = 0, l = position.count; i < l; i += 3 ) { + + a = i; + b = i + 1; + c = i + 2; + + intersection = checkBufferGeometryIntersection( this, raycaster, ray, position, uv, a, b, c ); + + if ( intersection ) { + + intersection.index = a; // triangle number in positions buffer semantics + intersects.push( intersection ); + + } + + } + + } + + } else if ( geometry.isGeometry ) { + + var fvA, fvB, fvC; + var isMultiMaterial = Array.isArray( material ); + + var vertices = geometry.vertices; + var faces = geometry.faces; + var uvs; + + var faceVertexUvs = geometry.faceVertexUvs[ 0 ]; + if ( faceVertexUvs.length > 0 ) uvs = faceVertexUvs; + + for ( var f = 0, fl = faces.length; f < fl; f ++ ) { + + var face = faces[ f ]; + var faceMaterial = isMultiMaterial ? material[ face.materialIndex ] : material; + + if ( faceMaterial === undefined ) continue; + + fvA = vertices[ face.a ]; + fvB = vertices[ face.b ]; + fvC = vertices[ face.c ]; + + if ( faceMaterial.morphTargets === true ) { + + var morphTargets = geometry.morphTargets; + var morphInfluences = this.morphTargetInfluences; + + vA.set( 0, 0, 0 ); + vB.set( 0, 0, 0 ); + vC.set( 0, 0, 0 ); + + for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) { + + var influence = morphInfluences[ t ]; + + if ( influence === 0 ) continue; + + var targets = morphTargets[ t ].vertices; + + vA.addScaledVector( tempA.subVectors( targets[ face.a ], fvA ), influence ); + vB.addScaledVector( tempB.subVectors( targets[ face.b ], fvB ), influence ); + vC.addScaledVector( tempC.subVectors( targets[ face.c ], fvC ), influence ); + + } + + vA.add( fvA ); + vB.add( fvB ); + vC.add( fvC ); + + fvA = vA; + fvB = vB; + fvC = vC; + + } + + intersection = checkIntersection( this, faceMaterial, raycaster, ray, fvA, fvB, fvC, intersectionPoint ); + + if ( intersection ) { + + if ( uvs && uvs[ f ] ) { + + var uvs_f = uvs[ f ]; + uvA.copy( uvs_f[ 0 ] ); + uvB.copy( uvs_f[ 1 ] ); + uvC.copy( uvs_f[ 2 ] ); + + intersection.uv = uvIntersection( intersectionPoint, fvA, fvB, fvC, uvA, uvB, uvC ); + + } + + intersection.face = face; + intersection.faceIndex = f; + intersects.push( intersection ); + + } + + } + + } + + }; + + }() ), + + clone: function () { + + return new this.constructor( this.geometry, this.material ).copy( this ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLBackground( renderer, state, geometries, premultipliedAlpha ) { + + var clearColor = new Color( 0x000000 ); + var clearAlpha = 0; + + var planeCamera, planeMesh; + var boxMesh; + + function render( renderList, scene, camera, forceClear ) { + + var background = scene.background; + + if ( background === null ) { + + setClear( clearColor, clearAlpha ); + + } else if ( background && background.isColor ) { + + setClear( background, 1 ); + forceClear = true; + + } + + if ( renderer.autoClear || forceClear ) { + + renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil ); + + } + + if ( background && background.isCubeTexture ) { + + if ( boxMesh === undefined ) { + + boxMesh = new Mesh( + new BoxBufferGeometry( 1, 1, 1 ), + new ShaderMaterial( { + uniforms: ShaderLib.cube.uniforms, + vertexShader: ShaderLib.cube.vertexShader, + fragmentShader: ShaderLib.cube.fragmentShader, + side: BackSide, + depthTest: true, + depthWrite: false, + fog: false + } ) + ); + + boxMesh.geometry.removeAttribute( 'normal' ); + boxMesh.geometry.removeAttribute( 'uv' ); + + boxMesh.onBeforeRender = function ( renderer, scene, camera ) { + + this.matrixWorld.copyPosition( camera.matrixWorld ); + + }; + + geometries.update( boxMesh.geometry ); + + } + + boxMesh.material.uniforms.tCube.value = background; + + renderList.push( boxMesh, boxMesh.geometry, boxMesh.material, 0, null ); + + } else if ( background && background.isTexture ) { + + if ( planeCamera === undefined ) { + + planeCamera = new OrthographicCamera( - 1, 1, 1, - 1, 0, 1 ); + + planeMesh = new Mesh( + new PlaneBufferGeometry( 2, 2 ), + new MeshBasicMaterial( { depthTest: false, depthWrite: false, fog: false } ) + ); + + geometries.update( planeMesh.geometry ); + + } + + planeMesh.material.map = background; + + // TODO Push this to renderList + + renderer.renderBufferDirect( planeCamera, null, planeMesh.geometry, planeMesh.material, planeMesh, null ); + + } + + } + + function setClear( color, alpha ) { + + state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha ); + + } + + return { + + getClearColor: function () { + + return clearColor; + + }, + setClearColor: function ( color, alpha ) { + + clearColor.set( color ); + clearAlpha = alpha !== undefined ? alpha : 1; + setClear( clearColor, clearAlpha ); + + }, + getClearAlpha: function () { + + return clearAlpha; + + }, + setClearAlpha: function ( alpha ) { + + clearAlpha = alpha; + setClear( clearColor, clearAlpha ); + + }, + render: render + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function painterSortStable( a, b ) { + + if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } else if ( a.program && b.program && a.program !== b.program ) { + + return a.program.id - b.program.id; + + } else if ( a.material.id !== b.material.id ) { + + return a.material.id - b.material.id; + + } else if ( a.z !== b.z ) { + + return a.z - b.z; + + } else { + + return a.id - b.id; + + } + + } + + function reversePainterSortStable( a, b ) { + + if ( a.renderOrder !== b.renderOrder ) { + + return a.renderOrder - b.renderOrder; + + } if ( a.z !== b.z ) { + + return b.z - a.z; + + } else { + + return a.id - b.id; + + } + + } + + function WebGLRenderList() { + + var renderItems = []; + var renderItemsIndex = 0; + + var opaque = []; + var transparent = []; + + function init() { + + renderItemsIndex = 0; + + opaque.length = 0; + transparent.length = 0; + + } + + function push( object, geometry, material, z, group ) { + + var renderItem = renderItems[ renderItemsIndex ]; + + if ( renderItem === undefined ) { + + renderItem = { + id: object.id, + object: object, + geometry: geometry, + material: material, + program: material.program, + renderOrder: object.renderOrder, + z: z, + group: group + }; + + renderItems[ renderItemsIndex ] = renderItem; + + } else { + + renderItem.id = object.id; + renderItem.object = object; + renderItem.geometry = geometry; + renderItem.material = material; + renderItem.program = material.program; + renderItem.renderOrder = object.renderOrder; + renderItem.z = z; + renderItem.group = group; + + } + + ( material.transparent === true ? transparent : opaque ).push( renderItem ); + + renderItemsIndex ++; + + } + + function sort() { + + if ( opaque.length > 1 ) opaque.sort( painterSortStable ); + if ( transparent.length > 1 ) transparent.sort( reversePainterSortStable ); + + } + + return { + opaque: opaque, + transparent: transparent, + + init: init, + push: push, + + sort: sort + }; + + } + + function WebGLRenderLists() { + + var lists = {}; + + function get( scene, camera ) { + + var hash = scene.id + ',' + camera.id; + var list = lists[ hash ]; + + if ( list === undefined ) { + + // console.log( 'THREE.WebGLRenderLists:', hash ); + + list = new WebGLRenderList(); + lists[ hash ] = list; + + } + + return list; + + } + + function dispose() { + + lists = {}; + + } + + return { + get: get, + dispose: dispose + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function absNumericalSort( a, b ) { + + return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] ); + + } + + function WebGLMorphtargets( gl ) { + + var influencesList = {}; + var morphInfluences = new Float32Array( 8 ); + + function update( object, geometry, material, program ) { + + var objectInfluences = object.morphTargetInfluences; + + var length = objectInfluences.length; + + var influences = influencesList[ geometry.id ]; + + if ( influences === undefined ) { + + // initialise list + + influences = []; + + for ( var i = 0; i < length; i ++ ) { + + influences[ i ] = [ i, 0 ]; + + } + + influencesList[ geometry.id ] = influences; + + } + + var morphTargets = material.morphTargets && geometry.morphAttributes.position; + var morphNormals = material.morphNormals && geometry.morphAttributes.normal; + + // Remove current morphAttributes + + for ( var i = 0; i < length; i ++ ) { + + var influence = influences[ i ]; + + if ( influence[ 1 ] !== 0 ) { + + if ( morphTargets ) geometry.removeAttribute( 'morphTarget' + i ); + if ( morphNormals ) geometry.removeAttribute( 'morphNormal' + i ); + + } + + } + + // Collect influences + + for ( var i = 0; i < length; i ++ ) { + + var influence = influences[ i ]; + + influence[ 0 ] = i; + influence[ 1 ] = objectInfluences[ i ]; + + } + + influences.sort( absNumericalSort ); + + // Add morphAttributes + + for ( var i = 0; i < 8; i ++ ) { + + var influence = influences[ i ]; + + if ( influence ) { + + var index = influence[ 0 ]; + var value = influence[ 1 ]; + + if ( value ) { + + if ( morphTargets ) geometry.addAttribute( 'morphTarget' + i, morphTargets[ index ] ); + if ( morphNormals ) geometry.addAttribute( 'morphNormal' + i, morphNormals[ index ] ); + + morphInfluences[ i ] = value; + continue; + + } + + } + + morphInfluences[ i ] = 0; + + } + + program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences ); + + } + + return { + + update: update + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLIndexedBufferRenderer( gl, extensions, infoRender ) { + + var mode; + + function setMode( value ) { + + mode = value; + + } + + var type, bytesPerElement; + + function setIndex( value ) { + + type = value.type; + bytesPerElement = value.bytesPerElement; + + } + + function render( start, count ) { + + gl.drawElements( mode, count, type, start * bytesPerElement ); + + infoRender.calls ++; + infoRender.vertices += count; + + if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3; + else if ( mode === gl.POINTS ) infoRender.points += count; + + } + + function renderInstances( geometry, start, count ) { + + var extension = extensions.get( 'ANGLE_instanced_arrays' ); + + if ( extension === null ) { + + console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; + + } + + extension.drawElementsInstancedANGLE( mode, count, type, start * bytesPerElement, geometry.maxInstancedCount ); + + infoRender.calls ++; + infoRender.vertices += count * geometry.maxInstancedCount; + + if ( mode === gl.TRIANGLES ) infoRender.faces += geometry.maxInstancedCount * count / 3; + else if ( mode === gl.POINTS ) infoRender.points += geometry.maxInstancedCount * count; + + } + + // + + this.setMode = setMode; + this.setIndex = setIndex; + this.render = render; + this.renderInstances = renderInstances; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLBufferRenderer( gl, extensions, infoRender ) { + + var mode; + + function setMode( value ) { + + mode = value; + + } + + function render( start, count ) { + + gl.drawArrays( mode, start, count ); + + infoRender.calls ++; + infoRender.vertices += count; + + if ( mode === gl.TRIANGLES ) infoRender.faces += count / 3; + else if ( mode === gl.POINTS ) infoRender.points += count; + + } + + function renderInstances( geometry, start, count ) { + + var extension = extensions.get( 'ANGLE_instanced_arrays' ); + + if ( extension === null ) { + + console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; + + } + + var position = geometry.attributes.position; + + if ( position.isInterleavedBufferAttribute ) { + + count = position.data.count; + + extension.drawArraysInstancedANGLE( mode, 0, count, geometry.maxInstancedCount ); + + } else { + + extension.drawArraysInstancedANGLE( mode, start, count, geometry.maxInstancedCount ); + + } + + infoRender.calls ++; + infoRender.vertices += count * geometry.maxInstancedCount; + + if ( mode === gl.TRIANGLES ) infoRender.faces += geometry.maxInstancedCount * count / 3; + else if ( mode === gl.POINTS ) infoRender.points += geometry.maxInstancedCount * count; + + } + + // + + this.setMode = setMode; + this.render = render; + this.renderInstances = renderInstances; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLGeometries( gl, attributes, infoMemory ) { + + var geometries = {}; + var wireframeAttributes = {}; + + function onGeometryDispose( event ) { + + var geometry = event.target; + var buffergeometry = geometries[ geometry.id ]; + + if ( buffergeometry.index !== null ) { + + attributes.remove( buffergeometry.index ); + + } + + for ( var name in buffergeometry.attributes ) { + + attributes.remove( buffergeometry.attributes[ name ] ); + + } + + geometry.removeEventListener( 'dispose', onGeometryDispose ); + + delete geometries[ geometry.id ]; + + // TODO Remove duplicate code + + var attribute = wireframeAttributes[ geometry.id ]; + + if ( attribute ) { + + attributes.remove( attribute ); + delete wireframeAttributes[ geometry.id ]; + + } + + attribute = wireframeAttributes[ buffergeometry.id ]; + + if ( attribute ) { + + attributes.remove( attribute ); + delete wireframeAttributes[ buffergeometry.id ]; + + } + + // + + infoMemory.geometries --; + + } + + function get( object, geometry ) { + + var buffergeometry = geometries[ geometry.id ]; + + if ( buffergeometry ) return buffergeometry; + + geometry.addEventListener( 'dispose', onGeometryDispose ); + + if ( geometry.isBufferGeometry ) { + + buffergeometry = geometry; + + } else if ( geometry.isGeometry ) { + + if ( geometry._bufferGeometry === undefined ) { + + geometry._bufferGeometry = new BufferGeometry().setFromObject( object ); + + } + + buffergeometry = geometry._bufferGeometry; + + } + + geometries[ geometry.id ] = buffergeometry; + + infoMemory.geometries ++; + + return buffergeometry; + + } + + function update( geometry ) { + + var index = geometry.index; + var geometryAttributes = geometry.attributes; + + if ( index !== null ) { + + attributes.update( index, gl.ELEMENT_ARRAY_BUFFER ); + + } + + for ( var name in geometryAttributes ) { + + attributes.update( geometryAttributes[ name ], gl.ARRAY_BUFFER ); + + } + + // morph targets + + var morphAttributes = geometry.morphAttributes; + + for ( var name in morphAttributes ) { + + var array = morphAttributes[ name ]; + + for ( var i = 0, l = array.length; i < l; i ++ ) { + + attributes.update( array[ i ], gl.ARRAY_BUFFER ); + + } + + } + + } + + function getWireframeAttribute( geometry ) { + + var attribute = wireframeAttributes[ geometry.id ]; + + if ( attribute ) return attribute; + + var indices = []; + + var geometryIndex = geometry.index; + var geometryAttributes = geometry.attributes; + + // console.time( 'wireframe' ); + + if ( geometryIndex !== null ) { + + var array = geometryIndex.array; + + for ( var i = 0, l = array.length; i < l; i += 3 ) { + + var a = array[ i + 0 ]; + var b = array[ i + 1 ]; + var c = array[ i + 2 ]; + + indices.push( a, b, b, c, c, a ); + + } + + } else { + + var array = geometryAttributes.position.array; + + for ( var i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) { + + var a = i + 0; + var b = i + 1; + var c = i + 2; + + indices.push( a, b, b, c, c, a ); + + } + + } + + // console.timeEnd( 'wireframe' ); + + attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 ); + + attributes.update( attribute, gl.ELEMENT_ARRAY_BUFFER ); + + wireframeAttributes[ geometry.id ] = attribute; + + return attribute; + + } + + return { + + get: get, + update: update, + + getWireframeAttribute: getWireframeAttribute + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLObjects( geometries, infoRender ) { + + var updateList = {}; + + function update( object ) { + + var frame = infoRender.frame; + + var geometry = object.geometry; + var buffergeometry = geometries.get( object, geometry ); + + // Update once per frame + + if ( updateList[ buffergeometry.id ] !== frame ) { + + if ( geometry.isGeometry ) { + + buffergeometry.updateFromObject( object ); + + } + + geometries.update( buffergeometry ); + + updateList[ buffergeometry.id ] = frame; + + } + + return buffergeometry; + + } + + function dispose() { + + updateList = {}; + + } + + return { + + update: update, + dispose: dispose + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function addLineNumbers( string ) { + + var lines = string.split( '\n' ); + + for ( var i = 0; i < lines.length; i ++ ) { + + lines[ i ] = ( i + 1 ) + ': ' + lines[ i ]; + + } + + return lines.join( '\n' ); + + } + + function WebGLShader( gl, type, string ) { + + var shader = gl.createShader( type ); + + gl.shaderSource( shader, string ); + gl.compileShader( shader ); + + if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === false ) { + + console.error( 'THREE.WebGLShader: Shader couldn\'t compile.' ); + + } + + if ( gl.getShaderInfoLog( shader ) !== '' ) { + + console.warn( 'THREE.WebGLShader: gl.getShaderInfoLog()', type === gl.VERTEX_SHADER ? 'vertex' : 'fragment', gl.getShaderInfoLog( shader ), addLineNumbers( string ) ); + + } + + // --enable-privileged-webgl-extension + // console.log( type, gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) ); + + return shader; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + var programIdCount = 0; + + function getEncodingComponents( encoding ) { + + switch ( encoding ) { + + case LinearEncoding: + return [ 'Linear', '( value )' ]; + case sRGBEncoding: + return [ 'sRGB', '( value )' ]; + case RGBEEncoding: + return [ 'RGBE', '( value )' ]; + case RGBM7Encoding: + return [ 'RGBM', '( value, 7.0 )' ]; + case RGBM16Encoding: + return [ 'RGBM', '( value, 16.0 )' ]; + case RGBDEncoding: + return [ 'RGBD', '( value, 256.0 )' ]; + case GammaEncoding: + return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ]; + default: + throw new Error( 'unsupported encoding: ' + encoding ); + + } + + } + + function getTexelDecodingFunction( functionName, encoding ) { + + var components = getEncodingComponents( encoding ); + return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }'; + + } + + function getTexelEncodingFunction( functionName, encoding ) { + + var components = getEncodingComponents( encoding ); + return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }'; + + } + + function getToneMappingFunction( functionName, toneMapping ) { + + var toneMappingName; + + switch ( toneMapping ) { + + case LinearToneMapping: + toneMappingName = 'Linear'; + break; + + case ReinhardToneMapping: + toneMappingName = 'Reinhard'; + break; + + case Uncharted2ToneMapping: + toneMappingName = 'Uncharted2'; + break; + + case CineonToneMapping: + toneMappingName = 'OptimizedCineon'; + break; + + default: + throw new Error( 'unsupported toneMapping: ' + toneMapping ); + + } + + return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }'; + + } + + function generateExtensions( extensions, parameters, rendererExtensions ) { + + extensions = extensions || {}; + + var chunks = [ + ( extensions.derivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.normalMap || parameters.flatShading ) ? '#extension GL_OES_standard_derivatives : enable' : '', + ( extensions.fragDepth || parameters.logarithmicDepthBuffer ) && rendererExtensions.get( 'EXT_frag_depth' ) ? '#extension GL_EXT_frag_depth : enable' : '', + ( extensions.drawBuffers ) && rendererExtensions.get( 'WEBGL_draw_buffers' ) ? '#extension GL_EXT_draw_buffers : require' : '', + ( extensions.shaderTextureLOD || parameters.envMap ) && rendererExtensions.get( 'EXT_shader_texture_lod' ) ? '#extension GL_EXT_shader_texture_lod : enable' : '' + ]; + + return chunks.filter( filterEmptyLine ).join( '\n' ); + + } + + function generateDefines( defines ) { + + var chunks = []; + + for ( var name in defines ) { + + var value = defines[ name ]; + + if ( value === false ) continue; + + chunks.push( '#define ' + name + ' ' + value ); + + } + + return chunks.join( '\n' ); + + } + + function fetchAttributeLocations( gl, program ) { + + var attributes = {}; + + var n = gl.getProgramParameter( program, gl.ACTIVE_ATTRIBUTES ); + + for ( var i = 0; i < n; i ++ ) { + + var info = gl.getActiveAttrib( program, i ); + var name = info.name; + + // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i ); + + attributes[ name ] = gl.getAttribLocation( program, name ); + + } + + return attributes; + + } + + function filterEmptyLine( string ) { + + return string !== ''; + + } + + function replaceLightNums( string, parameters ) { + + return string + .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights ) + .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights ) + .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights ) + .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights ) + .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights ); + + } + + function replaceClippingPlaneNums( string, parameters ) { + + return string + .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes ) + .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) ); + + } + + function parseIncludes( string ) { + + var pattern = /^[ \t]*#include +<([\w\d.]+)>/gm; + + function replace( match, include ) { + + var replace = ShaderChunk[ include ]; + + if ( replace === undefined ) { + + throw new Error( 'Can not resolve #include <' + include + '>' ); + + } + + return parseIncludes( replace ); + + } + + return string.replace( pattern, replace ); + + } + + function unrollLoops( string ) { + + var pattern = /#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g; + + function replace( match, start, end, snippet ) { + + var unroll = ''; + + for ( var i = parseInt( start ); i < parseInt( end ); i ++ ) { + + unroll += snippet.replace( /\[ i \]/g, '[ ' + i + ' ]' ); + + } + + return unroll; + + } + + return string.replace( pattern, replace ); + + } + + function WebGLProgram( renderer, extensions, code, material, shader, parameters ) { + + var gl = renderer.context; + + var defines = material.defines; + + var vertexShader = shader.vertexShader; + var fragmentShader = shader.fragmentShader; + + var shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC'; + + if ( parameters.shadowMapType === PCFShadowMap ) { + + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF'; + + } else if ( parameters.shadowMapType === PCFSoftShadowMap ) { + + shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT'; + + } + + var envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + var envMapModeDefine = 'ENVMAP_MODE_REFLECTION'; + var envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + + if ( parameters.envMap ) { + + switch ( material.envMap.mapping ) { + + case CubeReflectionMapping: + case CubeRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE'; + break; + + case CubeUVReflectionMapping: + case CubeUVRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV'; + break; + + case EquirectangularReflectionMapping: + case EquirectangularRefractionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_EQUIREC'; + break; + + case SphericalReflectionMapping: + envMapTypeDefine = 'ENVMAP_TYPE_SPHERE'; + break; + + } + + switch ( material.envMap.mapping ) { + + case CubeRefractionMapping: + case EquirectangularRefractionMapping: + envMapModeDefine = 'ENVMAP_MODE_REFRACTION'; + break; + + } + + switch ( material.combine ) { + + case MultiplyOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY'; + break; + + case MixOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_MIX'; + break; + + case AddOperation: + envMapBlendingDefine = 'ENVMAP_BLENDING_ADD'; + break; + + } + + } + + var gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0; + + // console.log( 'building new program ' ); + + // + + var customExtensions = generateExtensions( material.extensions, parameters, extensions ); + + var customDefines = generateDefines( defines ); + + // + + var program = gl.createProgram(); + + var prefixVertex, prefixFragment; + + if ( material.isRawShaderMaterial ) { + + prefixVertex = [ + + customDefines + + ].filter( filterEmptyLine ).join( '\n' ); + + if ( prefixVertex.length > 0 ) { + + prefixVertex += '\n'; + + } + + prefixFragment = [ + + customExtensions, + customDefines + + ].filter( filterEmptyLine ).join( '\n' ); + + if ( prefixFragment.length > 0 ) { + + prefixFragment += '\n'; + + } + + } else { + + prefixVertex = [ + + 'precision ' + parameters.precision + ' float;', + 'precision ' + parameters.precision + ' int;', + + '#define SHADER_NAME ' + shader.name, + + customDefines, + + parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '', + + '#define GAMMA_FACTOR ' + gammaFactorDefine, + + '#define MAX_BONES ' + parameters.maxBones, + ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', + ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '', + + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '', + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', + + parameters.flatShading ? '#define FLAT_SHADED' : '', + + parameters.skinning ? '#define USE_SKINNING' : '', + parameters.useVertexTexture ? '#define BONE_TEXTURE' : '', + + parameters.morphTargets ? '#define USE_MORPHTARGETS' : '', + parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '', + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', + + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', + + parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '', + + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + + 'uniform mat4 modelMatrix;', + 'uniform mat4 modelViewMatrix;', + 'uniform mat4 projectionMatrix;', + 'uniform mat4 viewMatrix;', + 'uniform mat3 normalMatrix;', + 'uniform vec3 cameraPosition;', + + 'attribute vec3 position;', + 'attribute vec3 normal;', + 'attribute vec2 uv;', + + '#ifdef USE_COLOR', + + ' attribute vec3 color;', + + '#endif', + + '#ifdef USE_MORPHTARGETS', + + ' attribute vec3 morphTarget0;', + ' attribute vec3 morphTarget1;', + ' attribute vec3 morphTarget2;', + ' attribute vec3 morphTarget3;', + + ' #ifdef USE_MORPHNORMALS', + + ' attribute vec3 morphNormal0;', + ' attribute vec3 morphNormal1;', + ' attribute vec3 morphNormal2;', + ' attribute vec3 morphNormal3;', + + ' #else', + + ' attribute vec3 morphTarget4;', + ' attribute vec3 morphTarget5;', + ' attribute vec3 morphTarget6;', + ' attribute vec3 morphTarget7;', + + ' #endif', + + '#endif', + + '#ifdef USE_SKINNING', + + ' attribute vec4 skinIndex;', + ' attribute vec4 skinWeight;', + + '#endif', + + '\n' + + ].filter( filterEmptyLine ).join( '\n' ); + + prefixFragment = [ + + customExtensions, + + 'precision ' + parameters.precision + ' float;', + 'precision ' + parameters.precision + ' int;', + + '#define SHADER_NAME ' + shader.name, + + customDefines, + + parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest : '', + + '#define GAMMA_FACTOR ' + gammaFactorDefine, + + ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '', + ( parameters.useFog && parameters.fogExp ) ? '#define FOG_EXP2' : '', + + parameters.map ? '#define USE_MAP' : '', + parameters.envMap ? '#define USE_ENVMAP' : '', + parameters.envMap ? '#define ' + envMapTypeDefine : '', + parameters.envMap ? '#define ' + envMapModeDefine : '', + parameters.envMap ? '#define ' + envMapBlendingDefine : '', + parameters.lightMap ? '#define USE_LIGHTMAP' : '', + parameters.aoMap ? '#define USE_AOMAP' : '', + parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '', + parameters.bumpMap ? '#define USE_BUMPMAP' : '', + parameters.normalMap ? '#define USE_NORMALMAP' : '', + parameters.specularMap ? '#define USE_SPECULARMAP' : '', + parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '', + parameters.metalnessMap ? '#define USE_METALNESSMAP' : '', + parameters.alphaMap ? '#define USE_ALPHAMAP' : '', + parameters.vertexColors ? '#define USE_COLOR' : '', + + parameters.gradientMap ? '#define USE_GRADIENTMAP' : '', + + parameters.flatShading ? '#define FLAT_SHADED' : '', + + parameters.doubleSided ? '#define DOUBLE_SIDED' : '', + parameters.flipSided ? '#define FLIP_SIDED' : '', + + parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '', + parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '', + + parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '', + + parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '', + + parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '', + parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '', + + parameters.envMap && extensions.get( 'EXT_shader_texture_lod' ) ? '#define TEXTURE_LOD_EXT' : '', + + 'uniform mat4 viewMatrix;', + 'uniform vec3 cameraPosition;', + + ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '', + ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below + ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '', + + parameters.dithering ? '#define DITHERING' : '', + + ( parameters.outputEncoding || parameters.mapEncoding || parameters.envMapEncoding || parameters.emissiveMapEncoding ) ? ShaderChunk[ 'encodings_pars_fragment' ] : '', // this code is required here because it is used by the various encoding/decoding function defined below + parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '', + parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '', + parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '', + parameters.outputEncoding ? getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ) : '', + + parameters.depthPacking ? '#define DEPTH_PACKING ' + material.depthPacking : '', + + '\n' + + ].filter( filterEmptyLine ).join( '\n' ); + + } + + vertexShader = parseIncludes( vertexShader ); + vertexShader = replaceLightNums( vertexShader, parameters ); + vertexShader = replaceClippingPlaneNums( vertexShader, parameters ); + + fragmentShader = parseIncludes( fragmentShader ); + fragmentShader = replaceLightNums( fragmentShader, parameters ); + fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters ); + + vertexShader = unrollLoops( vertexShader ); + fragmentShader = unrollLoops( fragmentShader ); + + var vertexGlsl = prefixVertex + vertexShader; + var fragmentGlsl = prefixFragment + fragmentShader; + + // console.log( '*VERTEX*', vertexGlsl ); + // console.log( '*FRAGMENT*', fragmentGlsl ); + + var glVertexShader = WebGLShader( gl, gl.VERTEX_SHADER, vertexGlsl ); + var glFragmentShader = WebGLShader( gl, gl.FRAGMENT_SHADER, fragmentGlsl ); + + gl.attachShader( program, glVertexShader ); + gl.attachShader( program, glFragmentShader ); + + // Force a particular attribute to index 0. + + if ( material.index0AttributeName !== undefined ) { + + gl.bindAttribLocation( program, 0, material.index0AttributeName ); + + } else if ( parameters.morphTargets === true ) { + + // programs with morphTargets displace position out of attribute 0 + gl.bindAttribLocation( program, 0, 'position' ); + + } + + gl.linkProgram( program ); + + var programLog = gl.getProgramInfoLog( program ).trim(); + var vertexLog = gl.getShaderInfoLog( glVertexShader ).trim(); + var fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim(); + + var runnable = true; + var haveDiagnostics = true; + + // console.log( '**VERTEX**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glVertexShader ) ); + // console.log( '**FRAGMENT**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( glFragmentShader ) ); + + if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === false ) { + + runnable = false; + + console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), 'gl.VALIDATE_STATUS', gl.getProgramParameter( program, gl.VALIDATE_STATUS ), 'gl.getProgramInfoLog', programLog, vertexLog, fragmentLog ); + + } else if ( programLog !== '' ) { + + console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog ); + + } else if ( vertexLog === '' || fragmentLog === '' ) { + + haveDiagnostics = false; + + } + + if ( haveDiagnostics ) { + + this.diagnostics = { + + runnable: runnable, + material: material, + + programLog: programLog, + + vertexShader: { + + log: vertexLog, + prefix: prefixVertex + + }, + + fragmentShader: { + + log: fragmentLog, + prefix: prefixFragment + + } + + }; + + } + + // clean up + + gl.deleteShader( glVertexShader ); + gl.deleteShader( glFragmentShader ); + + // set up caching for uniform locations + + var cachedUniforms; + + this.getUniforms = function () { + + if ( cachedUniforms === undefined ) { + + cachedUniforms = new WebGLUniforms( gl, program, renderer ); + + } + + return cachedUniforms; + + }; + + // set up caching for attribute locations + + var cachedAttributes; + + this.getAttributes = function () { + + if ( cachedAttributes === undefined ) { + + cachedAttributes = fetchAttributeLocations( gl, program ); + + } + + return cachedAttributes; + + }; + + // free resource + + this.destroy = function () { + + gl.deleteProgram( program ); + this.program = undefined; + + }; + + // DEPRECATED + + Object.defineProperties( this, { + + uniforms: { + get: function () { + + console.warn( 'THREE.WebGLProgram: .uniforms is now .getUniforms().' ); + return this.getUniforms(); + + } + }, + + attributes: { + get: function () { + + console.warn( 'THREE.WebGLProgram: .attributes is now .getAttributes().' ); + return this.getAttributes(); + + } + } + + } ); + + + // + + this.id = programIdCount ++; + this.code = code; + this.usedTimes = 1; + this.program = program; + this.vertexShader = glVertexShader; + this.fragmentShader = glFragmentShader; + + return this; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLPrograms( renderer, extensions, capabilities ) { + + var programs = []; + + var shaderIDs = { + MeshDepthMaterial: 'depth', + MeshDistanceMaterial: 'distanceRGBA', + MeshNormalMaterial: 'normal', + MeshBasicMaterial: 'basic', + MeshLambertMaterial: 'lambert', + MeshPhongMaterial: 'phong', + MeshToonMaterial: 'phong', + MeshStandardMaterial: 'physical', + MeshPhysicalMaterial: 'physical', + LineBasicMaterial: 'basic', + LineDashedMaterial: 'dashed', + PointsMaterial: 'points', + ShadowMaterial: 'shadow' + }; + + var parameterNames = [ + "precision", "supportsVertexTextures", "map", "mapEncoding", "envMap", "envMapMode", "envMapEncoding", + "lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "displacementMap", "specularMap", + "roughnessMap", "metalnessMap", "gradientMap", + "alphaMap", "combine", "vertexColors", "fog", "useFog", "fogExp", + "flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning", + "maxBones", "useVertexTexture", "morphTargets", "morphNormals", + "maxMorphTargets", "maxMorphNormals", "premultipliedAlpha", + "numDirLights", "numPointLights", "numSpotLights", "numHemiLights", "numRectAreaLights", + "shadowMapEnabled", "shadowMapType", "toneMapping", 'physicallyCorrectLights', + "alphaTest", "doubleSided", "flipSided", "numClippingPlanes", "numClipIntersection", "depthPacking", "dithering" + ]; + + + function allocateBones( object ) { + + var skeleton = object.skeleton; + var bones = skeleton.bones; + + if ( capabilities.floatVertexTextures ) { + + return 1024; + + } else { + + // default for when object is not specified + // ( for example when prebuilding shader to be used with multiple objects ) + // + // - leave some extra space for other uniforms + // - limit here is ANGLE's 254 max uniform vectors + // (up to 54 should be safe) + + var nVertexUniforms = capabilities.maxVertexUniforms; + var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); + + var maxBones = Math.min( nVertexMatrices, bones.length ); + + if ( maxBones < bones.length ) { + + console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' ); + return 0; + + } + + return maxBones; + + } + + } + + function getTextureEncodingFromMap( map, gammaOverrideLinear ) { + + var encoding; + + if ( ! map ) { + + encoding = LinearEncoding; + + } else if ( map.isTexture ) { + + encoding = map.encoding; + + } else if ( map.isWebGLRenderTarget ) { + + console.warn( "THREE.WebGLPrograms.getTextureEncodingFromMap: don't use render targets as textures. Use their .texture property instead." ); + encoding = map.texture.encoding; + + } + + // add backwards compatibility for WebGLRenderer.gammaInput/gammaOutput parameter, should probably be removed at some point. + if ( encoding === LinearEncoding && gammaOverrideLinear ) { + + encoding = GammaEncoding; + + } + + return encoding; + + } + + this.getParameters = function ( material, lights, shadows, fog, nClipPlanes, nClipIntersection, object ) { + + var shaderID = shaderIDs[ material.type ]; + + // heuristics to create shader parameters according to lights in the scene + // (not to blow over maxLights budget) + + var maxBones = object.isSkinnedMesh ? allocateBones( object ) : 0; + var precision = capabilities.precision; + + if ( material.precision !== null ) { + + precision = capabilities.getMaxPrecision( material.precision ); + + if ( precision !== material.precision ) { + + console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' ); + + } + + } + + var currentRenderTarget = renderer.getRenderTarget(); + + var parameters = { + + shaderID: shaderID, + + precision: precision, + supportsVertexTextures: capabilities.vertexTextures, + outputEncoding: getTextureEncodingFromMap( ( ! currentRenderTarget ) ? null : currentRenderTarget.texture, renderer.gammaOutput ), + map: !! material.map, + mapEncoding: getTextureEncodingFromMap( material.map, renderer.gammaInput ), + envMap: !! material.envMap, + envMapMode: material.envMap && material.envMap.mapping, + envMapEncoding: getTextureEncodingFromMap( material.envMap, renderer.gammaInput ), + envMapCubeUV: ( !! material.envMap ) && ( ( material.envMap.mapping === CubeUVReflectionMapping ) || ( material.envMap.mapping === CubeUVRefractionMapping ) ), + lightMap: !! material.lightMap, + aoMap: !! material.aoMap, + emissiveMap: !! material.emissiveMap, + emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap, renderer.gammaInput ), + bumpMap: !! material.bumpMap, + normalMap: !! material.normalMap, + displacementMap: !! material.displacementMap, + roughnessMap: !! material.roughnessMap, + metalnessMap: !! material.metalnessMap, + specularMap: !! material.specularMap, + alphaMap: !! material.alphaMap, + + gradientMap: !! material.gradientMap, + + combine: material.combine, + + vertexColors: material.vertexColors, + + fog: !! fog, + useFog: material.fog, + fogExp: ( fog && fog.isFogExp2 ), + + flatShading: material.flatShading, + + sizeAttenuation: material.sizeAttenuation, + logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer, + + skinning: material.skinning && maxBones > 0, + maxBones: maxBones, + useVertexTexture: capabilities.floatVertexTextures, + + morphTargets: material.morphTargets, + morphNormals: material.morphNormals, + maxMorphTargets: renderer.maxMorphTargets, + maxMorphNormals: renderer.maxMorphNormals, + + numDirLights: lights.directional.length, + numPointLights: lights.point.length, + numSpotLights: lights.spot.length, + numRectAreaLights: lights.rectArea.length, + numHemiLights: lights.hemi.length, + + numClippingPlanes: nClipPlanes, + numClipIntersection: nClipIntersection, + + dithering: material.dithering, + + shadowMapEnabled: renderer.shadowMap.enabled && object.receiveShadow && shadows.length > 0, + shadowMapType: renderer.shadowMap.type, + + toneMapping: renderer.toneMapping, + physicallyCorrectLights: renderer.physicallyCorrectLights, + + premultipliedAlpha: material.premultipliedAlpha, + + alphaTest: material.alphaTest, + doubleSided: material.side === DoubleSide, + flipSided: material.side === BackSide, + + depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false + + }; + + return parameters; + + }; + + this.getProgramCode = function ( material, parameters ) { + + var array = []; + + if ( parameters.shaderID ) { + + array.push( parameters.shaderID ); + + } else { + + array.push( material.fragmentShader ); + array.push( material.vertexShader ); + + } + + if ( material.defines !== undefined ) { + + for ( var name in material.defines ) { + + array.push( name ); + array.push( material.defines[ name ] ); + + } + + } + + for ( var i = 0; i < parameterNames.length; i ++ ) { + + array.push( parameters[ parameterNames[ i ] ] ); + + } + + array.push( material.onBeforeCompile.toString() ); + + array.push( renderer.gammaOutput ); + + return array.join(); + + }; + + this.acquireProgram = function ( material, shader, parameters, code ) { + + var program; + + // Check if code has been already compiled + for ( var p = 0, pl = programs.length; p < pl; p ++ ) { + + var programInfo = programs[ p ]; + + if ( programInfo.code === code ) { + + program = programInfo; + ++ program.usedTimes; + + break; + + } + + } + + if ( program === undefined ) { + + program = new WebGLProgram( renderer, extensions, code, material, shader, parameters ); + programs.push( program ); + + } + + return program; + + }; + + this.releaseProgram = function ( program ) { + + if ( -- program.usedTimes === 0 ) { + + // Remove from unordered set + var i = programs.indexOf( program ); + programs[ i ] = programs[ programs.length - 1 ]; + programs.pop(); + + // Free WebGL resources + program.destroy(); + + } + + }; + + // Exposed for resource monitoring & error feedback via renderer.info: + this.programs = programs; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, infoMemory, infoRender ) { + + var _isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && _gl instanceof WebGL2RenderingContext ); + var _videoTextures = {}; + var _canvas; + + // + + function clampToMaxSize( image, maxSize ) { + + if ( image.width > maxSize || image.height > maxSize ) { + + // Warning: Scaling through the canvas will only work with images that use + // premultiplied alpha. + + var scale = maxSize / Math.max( image.width, image.height ); + + var canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); + canvas.width = Math.floor( image.width * scale ); + canvas.height = Math.floor( image.height * scale ); + + var context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); + + console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); + + return canvas; + + } + + return image; + + } + + function isPowerOfTwo( image ) { + + return _Math.isPowerOfTwo( image.width ) && _Math.isPowerOfTwo( image.height ); + + } + + function makePowerOfTwo( image ) { + + if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof ImageBitmap ) { + + if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); + + _canvas.width = _Math.floorPowerOfTwo( image.width ); + _canvas.height = _Math.floorPowerOfTwo( image.height ); + + var context = _canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, _canvas.width, _canvas.height ); + + console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + _canvas.width + 'x' + _canvas.height, image ); + + return _canvas; + + } + + return image; + + } + + function textureNeedsPowerOfTwo( texture ) { + + return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) || + ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ); + + } + + function textureNeedsGenerateMipmaps( texture, isPowerOfTwo ) { + + return texture.generateMipmaps && isPowerOfTwo && + texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter; + + } + + // Fallback filters for non-power-of-2 textures + + function filterFallback( f ) { + + if ( f === NearestFilter || f === NearestMipMapNearestFilter || f === NearestMipMapLinearFilter ) { + + return _gl.NEAREST; + + } + + return _gl.LINEAR; + + } + + // + + function onTextureDispose( event ) { + + var texture = event.target; + + texture.removeEventListener( 'dispose', onTextureDispose ); + + deallocateTexture( texture ); + + if ( texture.isVideoTexture ) { + + delete _videoTextures[ texture.id ]; + + } + + infoMemory.textures --; + + } + + function onRenderTargetDispose( event ) { + + var renderTarget = event.target; + + renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); + + deallocateRenderTarget( renderTarget ); + + infoMemory.textures --; + + } + + // + + function deallocateTexture( texture ) { + + var textureProperties = properties.get( texture ); + + if ( texture.image && textureProperties.__image__webglTextureCube ) { + + // cube texture + + _gl.deleteTexture( textureProperties.__image__webglTextureCube ); + + } else { + + // 2D texture + + if ( textureProperties.__webglInit === undefined ) return; + + _gl.deleteTexture( textureProperties.__webglTexture ); + + } + + // remove all webgl properties + properties.remove( texture ); + + } + + function deallocateRenderTarget( renderTarget ) { + + var renderTargetProperties = properties.get( renderTarget ); + var textureProperties = properties.get( renderTarget.texture ); + + if ( ! renderTarget ) return; + + if ( textureProperties.__webglTexture !== undefined ) { + + _gl.deleteTexture( textureProperties.__webglTexture ); + + } + + if ( renderTarget.depthTexture ) { + + renderTarget.depthTexture.dispose(); + + } + + if ( renderTarget.isWebGLRenderTargetCube ) { + + for ( var i = 0; i < 6; i ++ ) { + + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); + + } + + } else { + + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); + + } + + properties.remove( renderTarget.texture ); + properties.remove( renderTarget ); + + } + + // + + + + function setTexture2D( texture, slot ) { + + var textureProperties = properties.get( texture ); + + if ( texture.isVideoTexture ) updateVideoTexture( texture ); + + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + + var image = texture.image; + + if ( image === undefined ) { + + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture ); + + } else if ( image.complete === false ) { + + console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture ); + + } else { + + uploadTexture( textureProperties, texture, slot ); + return; + + } + + } + + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); + + } + + function setTextureCube( texture, slot ) { + + var textureProperties = properties.get( texture ); + + if ( texture.image.length === 6 ) { + + if ( texture.version > 0 && textureProperties.__version !== texture.version ) { + + if ( ! textureProperties.__image__webglTextureCube ) { + + texture.addEventListener( 'dispose', onTextureDispose ); + + textureProperties.__image__webglTextureCube = _gl.createTexture(); + + infoMemory.textures ++; + + } + + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + + var isCompressed = ( texture && texture.isCompressedTexture ); + var isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture ); + + var cubeImage = []; + + for ( var i = 0; i < 6; i ++ ) { + + if ( ! isCompressed && ! isDataTexture ) { + + cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize ); + + } else { + + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; + + } + + } + + var image = cubeImage[ 0 ], + isPowerOfTwoImage = isPowerOfTwo( image ), + glFormat = utils.convert( texture.format ), + glType = utils.convert( texture.type ); + + setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isPowerOfTwoImage ); + + for ( var i = 0; i < 6; i ++ ) { + + if ( ! isCompressed ) { + + if ( isDataTexture ) { + + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); + + } else { + + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); + + } + + } else { + + var mipmap, mipmaps = cubeImage[ i ].mipmaps; + + for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { + + mipmap = mipmaps[ j ]; + + if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) { + + if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { + + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' ); + + } + + } else { + + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + + } + + } + + } + + } + + if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) { + + _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + + } + + textureProperties.__version = texture.version; + + if ( texture.onUpdate ) texture.onUpdate( texture ); + + } else { + + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); + + } + + } + + } + + function setTextureCubeDynamic( texture, slot ) { + + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture ); + + } + + function setTextureParameters( textureType, texture, isPowerOfTwoImage ) { + + var extension; + + if ( isPowerOfTwoImage ) { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, utils.convert( texture.wrapS ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, utils.convert( texture.wrapT ) ); + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, utils.convert( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, utils.convert( texture.minFilter ) ); + + } else { + + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); + _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); + + if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) { + + console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.', texture ); + + } + + _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); + _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); + + if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) { + + console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.', texture ); + + } + + } + + extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + + if ( extension ) { + + if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return; + if ( texture.type === HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return; + + if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { + + _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) ); + properties.get( texture ).__currentAnisotropy = texture.anisotropy; + + } + + } + + } + + function uploadTexture( textureProperties, texture, slot ) { + + if ( textureProperties.__webglInit === undefined ) { + + textureProperties.__webglInit = true; + + texture.addEventListener( 'dispose', onTextureDispose ); + + textureProperties.__webglTexture = _gl.createTexture(); + + infoMemory.textures ++; + + } + + state.activeTexture( _gl.TEXTURE0 + slot ); + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); + + _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); + _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); + _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); + + var image = clampToMaxSize( texture.image, capabilities.maxTextureSize ); + + if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) { + + image = makePowerOfTwo( image ); + + } + + var isPowerOfTwoImage = isPowerOfTwo( image ), + glFormat = utils.convert( texture.format ), + glType = utils.convert( texture.type ); + + setTextureParameters( _gl.TEXTURE_2D, texture, isPowerOfTwoImage ); + + var mipmap, mipmaps = texture.mipmaps; + + if ( texture.isDepthTexture ) { + + // populate depth texture with dummy data + + var internalFormat = _gl.DEPTH_COMPONENT; + + if ( texture.type === FloatType ) { + + if ( ! _isWebGL2 ) throw new Error( 'Float Depth Texture only supported in WebGL2.0' ); + internalFormat = _gl.DEPTH_COMPONENT32F; + + } else if ( _isWebGL2 ) { + + // WebGL 2.0 requires signed internalformat for glTexImage2D + internalFormat = _gl.DEPTH_COMPONENT16; + + } + + if ( texture.format === DepthFormat && internalFormat === _gl.DEPTH_COMPONENT ) { + + // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are + // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT + // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) + if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { + + console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); + + texture.type = UnsignedShortType; + glType = utils.convert( texture.type ); + + } + + } + + // Depth stencil textures need the DEPTH_STENCIL internal format + // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) + if ( texture.format === DepthStencilFormat ) { + + internalFormat = _gl.DEPTH_STENCIL; + + // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are + // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. + // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) + if ( texture.type !== UnsignedInt248Type ) { + + console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); + + texture.type = UnsignedInt248Type; + glType = utils.convert( texture.type ); + + } + + } + + state.texImage2D( _gl.TEXTURE_2D, 0, internalFormat, image.width, image.height, 0, glFormat, glType, null ); + + } else if ( texture.isDataTexture ) { + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isPowerOfTwoImage ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + + } + + texture.generateMipmaps = false; + + } else { + + state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); + + } + + } else if ( texture.isCompressedTexture ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + + if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) { + + if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { + + state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + + } else { + + console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' ); + + } + + } else { + + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + + } + + } + + } else { + + // regular Texture (image, video, canvas) + + // use manually created mipmaps if available + // if there are no manual mipmaps + // set 0 level mipmap and then use GL to generate other mipmap levels + + if ( mipmaps.length > 0 && isPowerOfTwoImage ) { + + for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { + + mipmap = mipmaps[ i ]; + state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); + + } + + texture.generateMipmaps = false; + + } else { + + state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image ); + + } + + } + + if ( textureNeedsGenerateMipmaps( texture, isPowerOfTwoImage ) ) _gl.generateMipmap( _gl.TEXTURE_2D ); + + textureProperties.__version = texture.version; + + if ( texture.onUpdate ) texture.onUpdate( texture ); + + } + + // Render targets + + // Setup storage for target texture and bind it to correct framebuffer + function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) { + + var glFormat = utils.convert( renderTarget.texture.format ); + var glType = utils.convert( renderTarget.texture.type ); + state.texImage2D( textureTarget, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 ); + _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); + + } + + // Setup storage for internal depth/stencil buffers and bind to correct framebuffer + function setupRenderBufferStorage( renderbuffer, renderTarget ) { + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); + + if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { + + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); + _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); + + } else { + + // FIXME: We don't support !depth !stencil + _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); + + } + + _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); + + } + + // Setup resources for a Depth Texture for a FBO (needs an extension) + function setupDepthTexture( framebuffer, renderTarget ) { + + var isCube = ( renderTarget && renderTarget.isWebGLRenderTargetCube ); + if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' ); + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + + if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) { + + throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' ); + + } + + // upload an empty depth texture with framebuffer size + if ( ! properties.get( renderTarget.depthTexture ).__webglTexture || + renderTarget.depthTexture.image.width !== renderTarget.width || + renderTarget.depthTexture.image.height !== renderTarget.height ) { + + renderTarget.depthTexture.image.width = renderTarget.width; + renderTarget.depthTexture.image.height = renderTarget.height; + renderTarget.depthTexture.needsUpdate = true; + + } + + setTexture2D( renderTarget.depthTexture, 0 ); + + var webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture; + + if ( renderTarget.depthTexture.format === DepthFormat ) { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) { + + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.TEXTURE_2D, webglDepthTexture, 0 ); + + } else { + + throw new Error( 'Unknown depthTexture format' ); + + } + + } + + // Setup GL resources for a non-texture depth buffer + function setupDepthRenderbuffer( renderTarget ) { + + var renderTargetProperties = properties.get( renderTarget ); + + var isCube = ( renderTarget.isWebGLRenderTargetCube === true ); + + if ( renderTarget.depthTexture ) { + + if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' ); + + setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget ); + + } else { + + if ( isCube ) { + + renderTargetProperties.__webglDepthbuffer = []; + + for ( var i = 0; i < 6; i ++ ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); + renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget ); + + } + + } else { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); + renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); + setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget ); + + } + + } + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); + + } + + // Set up GL resources for the render target + function setupRenderTarget( renderTarget ) { + + var renderTargetProperties = properties.get( renderTarget ); + var textureProperties = properties.get( renderTarget.texture ); + + renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); + + textureProperties.__webglTexture = _gl.createTexture(); + + infoMemory.textures ++; + + var isCube = ( renderTarget.isWebGLRenderTargetCube === true ); + var isTargetPowerOfTwo = isPowerOfTwo( renderTarget ); + + // Setup framebuffer + + if ( isCube ) { + + renderTargetProperties.__webglFramebuffer = []; + + for ( var i = 0; i < 6; i ++ ) { + + renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); + + } + + } else { + + renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); + + } + + // Setup color buffer + + if ( isCube ) { + + state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo ); + + for ( var i = 0; i < 6; i ++ ) { + + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); + + } + + if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); + state.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); + + } else { + + state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); + setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); + + if ( textureNeedsGenerateMipmaps( renderTarget.texture, isTargetPowerOfTwo ) ) _gl.generateMipmap( _gl.TEXTURE_2D ); + state.bindTexture( _gl.TEXTURE_2D, null ); + + } + + // Setup depth and stencil buffers + + if ( renderTarget.depthBuffer ) { + + setupDepthRenderbuffer( renderTarget ); + + } + + } + + function updateRenderTargetMipmap( renderTarget ) { + + var texture = renderTarget.texture; + var isTargetPowerOfTwo = isPowerOfTwo( renderTarget ); + + if ( textureNeedsGenerateMipmaps( texture, isTargetPowerOfTwo ) ) { + + var target = renderTarget.isWebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; + var webglTexture = properties.get( texture ).__webglTexture; + + state.bindTexture( target, webglTexture ); + _gl.generateMipmap( target ); + state.bindTexture( target, null ); + + } + + } + + function updateVideoTexture( texture ) { + + var id = texture.id; + var frame = infoRender.frame; + + // Check the last frame we updated the VideoTexture + + if ( _videoTextures[ id ] !== frame ) { + + _videoTextures[ id ] = frame; + texture.update(); + + } + + } + + this.setTexture2D = setTexture2D; + this.setTextureCube = setTextureCube; + this.setTextureCubeDynamic = setTextureCubeDynamic; + this.setupRenderTarget = setupRenderTarget; + this.updateRenderTargetMipmap = updateRenderTargetMipmap; + + } + + /** + * @author fordacious / fordacious.github.io + */ + + function WebGLProperties() { + + var properties = new WeakMap(); + + function get( object ) { + + if ( properties.has( object ) === false ) { + + properties.set( object, {} ); + + } + + return properties.get( object ); + + } + + function remove( object ) { + + properties.delete( object ); + + } + + function update( object, key, value ) { + + properties.get( object )[ key ] = value; + + } + + function dispose() { + + properties = new WeakMap(); + + } + + return { + get: get, + remove: remove, + update: update, + dispose: dispose + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLState( gl, extensions, utils ) { + + function ColorBuffer() { + + var locked = false; + + var color = new Vector4(); + var currentColorMask = null; + var currentColorClear = new Vector4( 0, 0, 0, 0 ); + + return { + + setMask: function ( colorMask ) { + + if ( currentColorMask !== colorMask && ! locked ) { + + gl.colorMask( colorMask, colorMask, colorMask, colorMask ); + currentColorMask = colorMask; + + } + + }, + + setLocked: function ( lock ) { + + locked = lock; + + }, + + setClear: function ( r, g, b, a, premultipliedAlpha ) { + + if ( premultipliedAlpha === true ) { + + r *= a; g *= a; b *= a; + + } + + color.set( r, g, b, a ); + + if ( currentColorClear.equals( color ) === false ) { + + gl.clearColor( r, g, b, a ); + currentColorClear.copy( color ); + + } + + }, + + reset: function () { + + locked = false; + + currentColorMask = null; + currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state + + } + + }; + + } + + function DepthBuffer() { + + var locked = false; + + var currentDepthMask = null; + var currentDepthFunc = null; + var currentDepthClear = null; + + return { + + setTest: function ( depthTest ) { + + if ( depthTest ) { + + enable( gl.DEPTH_TEST ); + + } else { + + disable( gl.DEPTH_TEST ); + + } + + }, + + setMask: function ( depthMask ) { + + if ( currentDepthMask !== depthMask && ! locked ) { + + gl.depthMask( depthMask ); + currentDepthMask = depthMask; + + } + + }, + + setFunc: function ( depthFunc ) { + + if ( currentDepthFunc !== depthFunc ) { + + if ( depthFunc ) { + + switch ( depthFunc ) { + + case NeverDepth: + + gl.depthFunc( gl.NEVER ); + break; + + case AlwaysDepth: + + gl.depthFunc( gl.ALWAYS ); + break; + + case LessDepth: + + gl.depthFunc( gl.LESS ); + break; + + case LessEqualDepth: + + gl.depthFunc( gl.LEQUAL ); + break; + + case EqualDepth: + + gl.depthFunc( gl.EQUAL ); + break; + + case GreaterEqualDepth: + + gl.depthFunc( gl.GEQUAL ); + break; + + case GreaterDepth: + + gl.depthFunc( gl.GREATER ); + break; + + case NotEqualDepth: + + gl.depthFunc( gl.NOTEQUAL ); + break; + + default: + + gl.depthFunc( gl.LEQUAL ); + + } + + } else { + + gl.depthFunc( gl.LEQUAL ); + + } + + currentDepthFunc = depthFunc; + + } + + }, + + setLocked: function ( lock ) { + + locked = lock; + + }, + + setClear: function ( depth ) { + + if ( currentDepthClear !== depth ) { + + gl.clearDepth( depth ); + currentDepthClear = depth; + + } + + }, + + reset: function () { + + locked = false; + + currentDepthMask = null; + currentDepthFunc = null; + currentDepthClear = null; + + } + + }; + + } + + function StencilBuffer() { + + var locked = false; + + var currentStencilMask = null; + var currentStencilFunc = null; + var currentStencilRef = null; + var currentStencilFuncMask = null; + var currentStencilFail = null; + var currentStencilZFail = null; + var currentStencilZPass = null; + var currentStencilClear = null; + + return { + + setTest: function ( stencilTest ) { + + if ( stencilTest ) { + + enable( gl.STENCIL_TEST ); + + } else { + + disable( gl.STENCIL_TEST ); + + } + + }, + + setMask: function ( stencilMask ) { + + if ( currentStencilMask !== stencilMask && ! locked ) { + + gl.stencilMask( stencilMask ); + currentStencilMask = stencilMask; + + } + + }, + + setFunc: function ( stencilFunc, stencilRef, stencilMask ) { + + if ( currentStencilFunc !== stencilFunc || + currentStencilRef !== stencilRef || + currentStencilFuncMask !== stencilMask ) { + + gl.stencilFunc( stencilFunc, stencilRef, stencilMask ); + + currentStencilFunc = stencilFunc; + currentStencilRef = stencilRef; + currentStencilFuncMask = stencilMask; + + } + + }, + + setOp: function ( stencilFail, stencilZFail, stencilZPass ) { + + if ( currentStencilFail !== stencilFail || + currentStencilZFail !== stencilZFail || + currentStencilZPass !== stencilZPass ) { + + gl.stencilOp( stencilFail, stencilZFail, stencilZPass ); + + currentStencilFail = stencilFail; + currentStencilZFail = stencilZFail; + currentStencilZPass = stencilZPass; + + } + + }, + + setLocked: function ( lock ) { + + locked = lock; + + }, + + setClear: function ( stencil ) { + + if ( currentStencilClear !== stencil ) { + + gl.clearStencil( stencil ); + currentStencilClear = stencil; + + } + + }, + + reset: function () { + + locked = false; + + currentStencilMask = null; + currentStencilFunc = null; + currentStencilRef = null; + currentStencilFuncMask = null; + currentStencilFail = null; + currentStencilZFail = null; + currentStencilZPass = null; + currentStencilClear = null; + + } + + }; + + } + + // + + var colorBuffer = new ColorBuffer(); + var depthBuffer = new DepthBuffer(); + var stencilBuffer = new StencilBuffer(); + + var maxVertexAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + var newAttributes = new Uint8Array( maxVertexAttributes ); + var enabledAttributes = new Uint8Array( maxVertexAttributes ); + var attributeDivisors = new Uint8Array( maxVertexAttributes ); + + var capabilities = {}; + + var compressedTextureFormats = null; + + var currentProgram = null; + + var currentBlending = null; + var currentBlendEquation = null; + var currentBlendSrc = null; + var currentBlendDst = null; + var currentBlendEquationAlpha = null; + var currentBlendSrcAlpha = null; + var currentBlendDstAlpha = null; + var currentPremultipledAlpha = false; + + var currentFlipSided = null; + var currentCullFace = null; + + var currentLineWidth = null; + + var currentPolygonOffsetFactor = null; + var currentPolygonOffsetUnits = null; + + var maxTextures = gl.getParameter( gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS ); + + var lineWidthAvailable = false; + var version = 0; + var glVersion = gl.getParameter( gl.VERSION ); + + if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) { + + version = parseFloat( /^WebGL\ ([0-9])/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 1.0 ); + + } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) { + + version = parseFloat( /^OpenGL\ ES\ ([0-9])/.exec( glVersion )[ 1 ] ); + lineWidthAvailable = ( version >= 2.0 ); + + } + + var currentTextureSlot = null; + var currentBoundTextures = {}; + + var currentScissor = new Vector4(); + var currentViewport = new Vector4(); + + function createTexture( type, target, count ) { + + var data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4. + var texture = gl.createTexture(); + + gl.bindTexture( type, texture ); + gl.texParameteri( type, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + gl.texParameteri( type, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + + for ( var i = 0; i < count; i ++ ) { + + gl.texImage2D( target + i, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, data ); + + } + + return texture; + + } + + var emptyTextures = {}; + emptyTextures[ gl.TEXTURE_2D ] = createTexture( gl.TEXTURE_2D, gl.TEXTURE_2D, 1 ); + emptyTextures[ gl.TEXTURE_CUBE_MAP ] = createTexture( gl.TEXTURE_CUBE_MAP, gl.TEXTURE_CUBE_MAP_POSITIVE_X, 6 ); + + // init + + colorBuffer.setClear( 0, 0, 0, 1 ); + depthBuffer.setClear( 1 ); + stencilBuffer.setClear( 0 ); + + enable( gl.DEPTH_TEST ); + depthBuffer.setFunc( LessEqualDepth ); + + setFlipSided( false ); + setCullFace( CullFaceBack ); + enable( gl.CULL_FACE ); + + enable( gl.BLEND ); + setBlending( NormalBlending ); + + // + + function initAttributes() { + + for ( var i = 0, l = newAttributes.length; i < l; i ++ ) { + + newAttributes[ i ] = 0; + + } + + } + + function enableAttribute( attribute ) { + + newAttributes[ attribute ] = 1; + + if ( enabledAttributes[ attribute ] === 0 ) { + + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; + + } + + if ( attributeDivisors[ attribute ] !== 0 ) { + + var extension = extensions.get( 'ANGLE_instanced_arrays' ); + + extension.vertexAttribDivisorANGLE( attribute, 0 ); + attributeDivisors[ attribute ] = 0; + + } + + } + + function enableAttributeAndDivisor( attribute, meshPerAttribute ) { + + newAttributes[ attribute ] = 1; + + if ( enabledAttributes[ attribute ] === 0 ) { + + gl.enableVertexAttribArray( attribute ); + enabledAttributes[ attribute ] = 1; + + } + + if ( attributeDivisors[ attribute ] !== meshPerAttribute ) { + + var extension = extensions.get( 'ANGLE_instanced_arrays' ); + + extension.vertexAttribDivisorANGLE( attribute, meshPerAttribute ); + attributeDivisors[ attribute ] = meshPerAttribute; + + } + + } + + function disableUnusedAttributes() { + + for ( var i = 0, l = enabledAttributes.length; i !== l; ++ i ) { + + if ( enabledAttributes[ i ] !== newAttributes[ i ] ) { + + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; + + } + + } + + } + + function enable( id ) { + + if ( capabilities[ id ] !== true ) { + + gl.enable( id ); + capabilities[ id ] = true; + + } + + } + + function disable( id ) { + + if ( capabilities[ id ] !== false ) { + + gl.disable( id ); + capabilities[ id ] = false; + + } + + } + + function getCompressedTextureFormats() { + + if ( compressedTextureFormats === null ) { + + compressedTextureFormats = []; + + if ( extensions.get( 'WEBGL_compressed_texture_pvrtc' ) || + extensions.get( 'WEBGL_compressed_texture_s3tc' ) || + extensions.get( 'WEBGL_compressed_texture_etc1' ) || + extensions.get( 'WEBGL_compressed_texture_astc' ) ) { + + var formats = gl.getParameter( gl.COMPRESSED_TEXTURE_FORMATS ); + + for ( var i = 0; i < formats.length; i ++ ) { + + compressedTextureFormats.push( formats[ i ] ); + + } + + } + + } + + return compressedTextureFormats; + + } + + function useProgram( program ) { + + if ( currentProgram !== program ) { + + gl.useProgram( program ); + + currentProgram = program; + + return true; + + } + + return false; + + } + + function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) { + + if ( blending !== NoBlending ) { + + enable( gl.BLEND ); + + } else { + + disable( gl.BLEND ); + + } + + if ( blending !== CustomBlending ) { + + if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) { + + switch ( blending ) { + + case AdditiveBlending: + + if ( premultipliedAlpha ) { + + gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); + gl.blendFuncSeparate( gl.ONE, gl.ONE, gl.ONE, gl.ONE ); + + } else { + + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.SRC_ALPHA, gl.ONE ); + + } + break; + + case SubtractiveBlending: + + if ( premultipliedAlpha ) { + + gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); + gl.blendFuncSeparate( gl.ZERO, gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ONE_MINUS_SRC_ALPHA ); + + } else { + + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ZERO, gl.ONE_MINUS_SRC_COLOR ); + + } + break; + + case MultiplyBlending: + + if ( premultipliedAlpha ) { + + gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); + gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA ); + + } else { + + gl.blendEquation( gl.FUNC_ADD ); + gl.blendFunc( gl.ZERO, gl.SRC_COLOR ); + + } + break; + + default: + + if ( premultipliedAlpha ) { + + gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); + gl.blendFuncSeparate( gl.ONE, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + + } else { + + gl.blendEquationSeparate( gl.FUNC_ADD, gl.FUNC_ADD ); + gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA ); + + } + + } + + } + + currentBlendEquation = null; + currentBlendSrc = null; + currentBlendDst = null; + currentBlendEquationAlpha = null; + currentBlendSrcAlpha = null; + currentBlendDstAlpha = null; + + } else { + + blendEquationAlpha = blendEquationAlpha || blendEquation; + blendSrcAlpha = blendSrcAlpha || blendSrc; + blendDstAlpha = blendDstAlpha || blendDst; + + if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) { + + gl.blendEquationSeparate( utils.convert( blendEquation ), utils.convert( blendEquationAlpha ) ); + + currentBlendEquation = blendEquation; + currentBlendEquationAlpha = blendEquationAlpha; + + } + + if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) { + + gl.blendFuncSeparate( utils.convert( blendSrc ), utils.convert( blendDst ), utils.convert( blendSrcAlpha ), utils.convert( blendDstAlpha ) ); + + currentBlendSrc = blendSrc; + currentBlendDst = blendDst; + currentBlendSrcAlpha = blendSrcAlpha; + currentBlendDstAlpha = blendDstAlpha; + + } + + } + + currentBlending = blending; + currentPremultipledAlpha = premultipliedAlpha; + + } + + function setMaterial( material, frontFaceCW ) { + + material.side === DoubleSide + ? disable( gl.CULL_FACE ) + : enable( gl.CULL_FACE ); + + var flipSided = ( material.side === BackSide ); + if ( frontFaceCW ) flipSided = ! flipSided; + + setFlipSided( flipSided ); + + material.transparent === true + ? setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha ) + : setBlending( NoBlending ); + + depthBuffer.setFunc( material.depthFunc ); + depthBuffer.setTest( material.depthTest ); + depthBuffer.setMask( material.depthWrite ); + colorBuffer.setMask( material.colorWrite ); + + setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); + + } + + // + + function setFlipSided( flipSided ) { + + if ( currentFlipSided !== flipSided ) { + + if ( flipSided ) { + + gl.frontFace( gl.CW ); + + } else { + + gl.frontFace( gl.CCW ); + + } + + currentFlipSided = flipSided; + + } + + } + + function setCullFace( cullFace ) { + + if ( cullFace !== CullFaceNone ) { + + enable( gl.CULL_FACE ); + + if ( cullFace !== currentCullFace ) { + + if ( cullFace === CullFaceBack ) { + + gl.cullFace( gl.BACK ); + + } else if ( cullFace === CullFaceFront ) { + + gl.cullFace( gl.FRONT ); + + } else { + + gl.cullFace( gl.FRONT_AND_BACK ); + + } + + } + + } else { + + disable( gl.CULL_FACE ); + + } + + currentCullFace = cullFace; + + } + + function setLineWidth( width ) { + + if ( width !== currentLineWidth ) { + + if ( lineWidthAvailable ) gl.lineWidth( width ); + + currentLineWidth = width; + + } + + } + + function setPolygonOffset( polygonOffset, factor, units ) { + + if ( polygonOffset ) { + + enable( gl.POLYGON_OFFSET_FILL ); + + if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) { + + gl.polygonOffset( factor, units ); + + currentPolygonOffsetFactor = factor; + currentPolygonOffsetUnits = units; + + } + + } else { + + disable( gl.POLYGON_OFFSET_FILL ); + + } + + } + + function setScissorTest( scissorTest ) { + + if ( scissorTest ) { + + enable( gl.SCISSOR_TEST ); + + } else { + + disable( gl.SCISSOR_TEST ); + + } + + } + + // texture + + function activeTexture( webglSlot ) { + + if ( webglSlot === undefined ) webglSlot = gl.TEXTURE0 + maxTextures - 1; + + if ( currentTextureSlot !== webglSlot ) { + + gl.activeTexture( webglSlot ); + currentTextureSlot = webglSlot; + + } + + } + + function bindTexture( webglType, webglTexture ) { + + if ( currentTextureSlot === null ) { + + activeTexture(); + + } + + var boundTexture = currentBoundTextures[ currentTextureSlot ]; + + if ( boundTexture === undefined ) { + + boundTexture = { type: undefined, texture: undefined }; + currentBoundTextures[ currentTextureSlot ] = boundTexture; + + } + + if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) { + + gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] ); + + boundTexture.type = webglType; + boundTexture.texture = webglTexture; + + } + + } + + function compressedTexImage2D() { + + try { + + gl.compressedTexImage2D.apply( gl, arguments ); + + } catch ( error ) { + + console.error( 'THREE.WebGLState:', error ); + + } + + } + + function texImage2D() { + + try { + + gl.texImage2D.apply( gl, arguments ); + + } catch ( error ) { + + console.error( 'THREE.WebGLState:', error ); + + } + + } + + // + + function scissor( scissor ) { + + if ( currentScissor.equals( scissor ) === false ) { + + gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w ); + currentScissor.copy( scissor ); + + } + + } + + function viewport( viewport ) { + + if ( currentViewport.equals( viewport ) === false ) { + + gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w ); + currentViewport.copy( viewport ); + + } + + } + + // + + function reset() { + + for ( var i = 0; i < enabledAttributes.length; i ++ ) { + + if ( enabledAttributes[ i ] === 1 ) { + + gl.disableVertexAttribArray( i ); + enabledAttributes[ i ] = 0; + + } + + } + + capabilities = {}; + + compressedTextureFormats = null; + + currentTextureSlot = null; + currentBoundTextures = {}; + + currentProgram = null; + + currentBlending = null; + + currentFlipSided = null; + currentCullFace = null; + + colorBuffer.reset(); + depthBuffer.reset(); + stencilBuffer.reset(); + + } + + return { + + buffers: { + color: colorBuffer, + depth: depthBuffer, + stencil: stencilBuffer + }, + + initAttributes: initAttributes, + enableAttribute: enableAttribute, + enableAttributeAndDivisor: enableAttributeAndDivisor, + disableUnusedAttributes: disableUnusedAttributes, + enable: enable, + disable: disable, + getCompressedTextureFormats: getCompressedTextureFormats, + + useProgram: useProgram, + + setBlending: setBlending, + setMaterial: setMaterial, + + setFlipSided: setFlipSided, + setCullFace: setCullFace, + + setLineWidth: setLineWidth, + setPolygonOffset: setPolygonOffset, + + setScissorTest: setScissorTest, + + activeTexture: activeTexture, + bindTexture: bindTexture, + compressedTexImage2D: compressedTexImage2D, + texImage2D: texImage2D, + + scissor: scissor, + viewport: viewport, + + reset: reset + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLCapabilities( gl, extensions, parameters ) { + + var maxAnisotropy; + + function getMaxAnisotropy() { + + if ( maxAnisotropy !== undefined ) return maxAnisotropy; + + var extension = extensions.get( 'EXT_texture_filter_anisotropic' ); + + if ( extension !== null ) { + + maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); + + } else { + + maxAnisotropy = 0; + + } + + return maxAnisotropy; + + } + + function getMaxPrecision( precision ) { + + if ( precision === 'highp' ) { + + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.HIGH_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.HIGH_FLOAT ).precision > 0 ) { + + return 'highp'; + + } + + precision = 'mediump'; + + } + + if ( precision === 'mediump' ) { + + if ( gl.getShaderPrecisionFormat( gl.VERTEX_SHADER, gl.MEDIUM_FLOAT ).precision > 0 && + gl.getShaderPrecisionFormat( gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT ).precision > 0 ) { + + return 'mediump'; + + } + + } + + return 'lowp'; + + } + + var precision = parameters.precision !== undefined ? parameters.precision : 'highp'; + var maxPrecision = getMaxPrecision( precision ); + + if ( maxPrecision !== precision ) { + + console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' ); + precision = maxPrecision; + + } + + var logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true; + + var maxTextures = gl.getParameter( gl.MAX_TEXTURE_IMAGE_UNITS ); + var maxVertexTextures = gl.getParameter( gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ); + var maxTextureSize = gl.getParameter( gl.MAX_TEXTURE_SIZE ); + var maxCubemapSize = gl.getParameter( gl.MAX_CUBE_MAP_TEXTURE_SIZE ); + + var maxAttributes = gl.getParameter( gl.MAX_VERTEX_ATTRIBS ); + var maxVertexUniforms = gl.getParameter( gl.MAX_VERTEX_UNIFORM_VECTORS ); + var maxVaryings = gl.getParameter( gl.MAX_VARYING_VECTORS ); + var maxFragmentUniforms = gl.getParameter( gl.MAX_FRAGMENT_UNIFORM_VECTORS ); + + var vertexTextures = maxVertexTextures > 0; + var floatFragmentTextures = !! extensions.get( 'OES_texture_float' ); + var floatVertexTextures = vertexTextures && floatFragmentTextures; + + return { + + getMaxAnisotropy: getMaxAnisotropy, + getMaxPrecision: getMaxPrecision, + + precision: precision, + logarithmicDepthBuffer: logarithmicDepthBuffer, + + maxTextures: maxTextures, + maxVertexTextures: maxVertexTextures, + maxTextureSize: maxTextureSize, + maxCubemapSize: maxCubemapSize, + + maxAttributes: maxAttributes, + maxVertexUniforms: maxVertexUniforms, + maxVaryings: maxVaryings, + maxFragmentUniforms: maxFragmentUniforms, + + vertexTextures: vertexTextures, + floatFragmentTextures: floatFragmentTextures, + floatVertexTextures: floatVertexTextures + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + * @author greggman / http://games.greggman.com/ + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author tschw + */ + + function PerspectiveCamera( fov, aspect, near, far ) { + + Camera.call( this ); + + this.type = 'PerspectiveCamera'; + + this.fov = fov !== undefined ? fov : 50; + this.zoom = 1; + + this.near = near !== undefined ? near : 0.1; + this.far = far !== undefined ? far : 2000; + this.focus = 10; + + this.aspect = aspect !== undefined ? aspect : 1; + this.view = null; + + this.filmGauge = 35; // width of the film (default in millimeters) + this.filmOffset = 0; // horizontal film offset (same unit as gauge) + + this.updateProjectionMatrix(); + + } + + PerspectiveCamera.prototype = Object.assign( Object.create( Camera.prototype ), { + + constructor: PerspectiveCamera, + + isPerspectiveCamera: true, + + copy: function ( source, recursive ) { + + Camera.prototype.copy.call( this, source, recursive ); + + this.fov = source.fov; + this.zoom = source.zoom; + + this.near = source.near; + this.far = source.far; + this.focus = source.focus; + + this.aspect = source.aspect; + this.view = source.view === null ? null : Object.assign( {}, source.view ); + + this.filmGauge = source.filmGauge; + this.filmOffset = source.filmOffset; + + return this; + + }, + + /** + * Sets the FOV by focal length in respect to the current .filmGauge. + * + * The default film gauge is 35, so that the focal length can be specified for + * a 35mm (full frame) camera. + * + * Values for focal length and film gauge must have the same unit. + */ + setFocalLength: function ( focalLength ) { + + // see http://www.bobatkins.com/photography/technical/field_of_view.html + var vExtentSlope = 0.5 * this.getFilmHeight() / focalLength; + + this.fov = _Math.RAD2DEG * 2 * Math.atan( vExtentSlope ); + this.updateProjectionMatrix(); + + }, + + /** + * Calculates the focal length from the current .fov and .filmGauge. + */ + getFocalLength: function () { + + var vExtentSlope = Math.tan( _Math.DEG2RAD * 0.5 * this.fov ); + + return 0.5 * this.getFilmHeight() / vExtentSlope; + + }, + + getEffectiveFOV: function () { + + return _Math.RAD2DEG * 2 * Math.atan( + Math.tan( _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom ); + + }, + + getFilmWidth: function () { + + // film not completely covered in portrait format (aspect < 1) + return this.filmGauge * Math.min( this.aspect, 1 ); + + }, + + getFilmHeight: function () { + + // film not completely covered in landscape format (aspect > 1) + return this.filmGauge / Math.max( this.aspect, 1 ); + + }, + + /** + * Sets an offset in a larger frustum. This is useful for multi-window or + * multi-monitor/multi-machine setups. + * + * For example, if you have 3x2 monitors and each monitor is 1920x1080 and + * the monitors are in grid like this + * + * +---+---+---+ + * | A | B | C | + * +---+---+---+ + * | D | E | F | + * +---+---+---+ + * + * then for each monitor you would call it like this + * + * var w = 1920; + * var h = 1080; + * var fullWidth = w * 3; + * var fullHeight = h * 2; + * + * --A-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h ); + * --B-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h ); + * --C-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h ); + * --D-- + * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h ); + * --E-- + * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h ); + * --F-- + * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h ); + * + * Note there is no reason monitors have to be the same size or in a grid. + */ + setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) { + + this.aspect = fullWidth / fullHeight; + + if ( this.view === null ) { + + this.view = { + enabled: true, + fullWidth: 1, + fullHeight: 1, + offsetX: 0, + offsetY: 0, + width: 1, + height: 1 + }; + + } + + this.view.enabled = true; + this.view.fullWidth = fullWidth; + this.view.fullHeight = fullHeight; + this.view.offsetX = x; + this.view.offsetY = y; + this.view.width = width; + this.view.height = height; + + this.updateProjectionMatrix(); + + }, + + clearViewOffset: function () { + + if ( this.view !== null ) { + + this.view.enabled = false; + + } + + this.updateProjectionMatrix(); + + }, + + updateProjectionMatrix: function () { + + var near = this.near, + top = near * Math.tan( + _Math.DEG2RAD * 0.5 * this.fov ) / this.zoom, + height = 2 * top, + width = this.aspect * height, + left = - 0.5 * width, + view = this.view; + + if ( this.view !== null && this.view.enabled ) { + + var fullWidth = view.fullWidth, + fullHeight = view.fullHeight; + + left += view.offsetX * width / fullWidth; + top -= view.offsetY * height / fullHeight; + width *= view.width / fullWidth; + height *= view.height / fullHeight; + + } + + var skew = this.filmOffset; + if ( skew !== 0 ) left += near * skew / this.getFilmWidth(); + + this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far ); + + }, + + toJSON: function ( meta ) { + + var data = Object3D.prototype.toJSON.call( this, meta ); + + data.object.fov = this.fov; + data.object.zoom = this.zoom; + + data.object.near = this.near; + data.object.far = this.far; + data.object.focus = this.focus; + + data.object.aspect = this.aspect; + + if ( this.view !== null ) data.object.view = Object.assign( {}, this.view ); + + data.object.filmGauge = this.filmGauge; + data.object.filmOffset = this.filmOffset; + + return data; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function ArrayCamera( array ) { + + PerspectiveCamera.call( this ); + + this.cameras = array || []; + + } + + ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), { + + constructor: ArrayCamera, + + isArrayCamera: true + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebVRManager( renderer ) { + + var scope = this; + + var device = null; + var frameData = null; + + var poseTarget = null; + + var standingMatrix = new Matrix4(); + var standingMatrixInverse = new Matrix4(); + + if ( typeof window !== 'undefined' && 'VRFrameData' in window ) { + + frameData = new window.VRFrameData(); + + } + + var matrixWorldInverse = new Matrix4(); + + var cameraL = new PerspectiveCamera(); + cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 ); + cameraL.layers.enable( 1 ); + + var cameraR = new PerspectiveCamera(); + cameraR.bounds = new Vector4( 0.5, 0.0, 0.5, 1.0 ); + cameraR.layers.enable( 2 ); + + var cameraVR = new ArrayCamera( [ cameraL, cameraR ] ); + cameraVR.layers.enable( 1 ); + cameraVR.layers.enable( 2 ); + + // + + var currentSize, currentPixelRatio; + + function onVRDisplayPresentChange() { + + if ( device !== null && device.isPresenting ) { + + var eyeParameters = device.getEyeParameters( 'left' ); + var renderWidth = eyeParameters.renderWidth; + var renderHeight = eyeParameters.renderHeight; + + currentPixelRatio = renderer.getPixelRatio(); + currentSize = renderer.getSize(); + + renderer.setDrawingBufferSize( renderWidth * 2, renderHeight, 1 ); + + } else if ( scope.enabled ) { + + renderer.setDrawingBufferSize( currentSize.width, currentSize.height, currentPixelRatio ); + + } + + } + + if ( typeof window !== 'undefined' ) { + + window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false ); + + } + + // + + this.enabled = false; + this.userHeight = 1.6; + + this.getDevice = function () { + + return device; + + }; + + this.setDevice = function ( value ) { + + if ( value !== undefined ) device = value; + + }; + + this.setPoseTarget = function ( object ) { + + if ( object !== undefined ) poseTarget = object; + + }; + + this.getCamera = function ( camera ) { + + if ( device === null ) return camera; + + device.depthNear = camera.near; + device.depthFar = camera.far; + + device.getFrameData( frameData ); + + // + + var pose = frameData.pose; + var poseObject = poseTarget !== null ? poseTarget : camera; + + if ( pose.position !== null ) { + + poseObject.position.fromArray( pose.position ); + + } else { + + poseObject.position.set( 0, 0, 0 ); + + } + + if ( pose.orientation !== null ) { + + poseObject.quaternion.fromArray( pose.orientation ); + + } + + var stageParameters = device.stageParameters; + + if ( stageParameters ) { + + standingMatrix.fromArray( stageParameters.sittingToStandingTransform ); + + } else { + + standingMatrix.makeTranslation( 0, scope.userHeight, 0 ); + + } + + poseObject.position.applyMatrix4( standingMatrix ); + poseObject.updateMatrixWorld(); + + if ( device.isPresenting === false ) return camera; + + // + + cameraL.near = camera.near; + cameraR.near = camera.near; + + cameraL.far = camera.far; + cameraR.far = camera.far; + + cameraVR.matrixWorld.copy( camera.matrixWorld ); + cameraVR.matrixWorldInverse.copy( camera.matrixWorldInverse ); + + cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix ); + cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix ); + + // TODO (mrdoob) Double check this code + + standingMatrixInverse.getInverse( standingMatrix ); + + cameraL.matrixWorldInverse.multiply( standingMatrixInverse ); + cameraR.matrixWorldInverse.multiply( standingMatrixInverse ); + + var parent = poseObject.parent; + + if ( parent !== null ) { + + matrixWorldInverse.getInverse( parent.matrixWorld ); + + cameraL.matrixWorldInverse.multiply( matrixWorldInverse ); + cameraR.matrixWorldInverse.multiply( matrixWorldInverse ); + + } + + // envMap and Mirror needs camera.matrixWorld + + cameraL.matrixWorld.getInverse( cameraL.matrixWorldInverse ); + cameraR.matrixWorld.getInverse( cameraR.matrixWorldInverse ); + + cameraL.projectionMatrix.fromArray( frameData.leftProjectionMatrix ); + cameraR.projectionMatrix.fromArray( frameData.rightProjectionMatrix ); + + // HACK (mrdoob) + // https://github.com/w3c/webvr/issues/203 + + cameraVR.projectionMatrix.copy( cameraL.projectionMatrix ); + + // + + var layers = device.getLayers(); + + if ( layers.length ) { + + var layer = layers[ 0 ]; + + if ( layer.leftBounds !== null && layer.leftBounds.length === 4 ) { + + cameraL.bounds.fromArray( layer.leftBounds ); + + } + + if ( layer.rightBounds !== null && layer.rightBounds.length === 4 ) { + + cameraR.bounds.fromArray( layer.rightBounds ); + + } + + } + + return cameraVR; + + }; + + this.getStandingMatrix = function () { + + return standingMatrix; + + }; + + this.submitFrame = function () { + + if ( device && device.isPresenting ) device.submitFrame(); + + }; + + this.dispose = function () { + + if ( typeof window !== 'undefined' ) { + + window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange ); + + } + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function WebGLExtensions( gl ) { + + var extensions = {}; + + return { + + get: function ( name ) { + + if ( extensions[ name ] !== undefined ) { + + return extensions[ name ]; + + } + + var extension; + + switch ( name ) { + + case 'WEBGL_depth_texture': + extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' ); + break; + + case 'EXT_texture_filter_anisotropic': + extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' ); + break; + + case 'WEBGL_compressed_texture_s3tc': + extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' ); + break; + + case 'WEBGL_compressed_texture_pvrtc': + extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ); + break; + + case 'WEBGL_compressed_texture_etc1': + extension = gl.getExtension( 'WEBGL_compressed_texture_etc1' ); + break; + + default: + extension = gl.getExtension( name ); + + } + + if ( extension === null ) { + + console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' ); + + } + + extensions[ name ] = extension; + + return extension; + + } + + }; + + } + + /** + * @author tschw + */ + + function WebGLClipping() { + + var scope = this, + + globalState = null, + numGlobalPlanes = 0, + localClippingEnabled = false, + renderingShadows = false, + + plane = new Plane(), + viewNormalMatrix = new Matrix3(), + + uniform = { value: null, needsUpdate: false }; + + this.uniform = uniform; + this.numPlanes = 0; + this.numIntersection = 0; + + this.init = function ( planes, enableLocalClipping, camera ) { + + var enabled = + planes.length !== 0 || + enableLocalClipping || + // enable state of previous frame - the clipping code has to + // run another frame in order to reset the state: + numGlobalPlanes !== 0 || + localClippingEnabled; + + localClippingEnabled = enableLocalClipping; + + globalState = projectPlanes( planes, camera, 0 ); + numGlobalPlanes = planes.length; + + return enabled; + + }; + + this.beginShadows = function () { + + renderingShadows = true; + projectPlanes( null ); + + }; + + this.endShadows = function () { + + renderingShadows = false; + resetGlobalState(); + + }; + + this.setState = function ( planes, clipIntersection, clipShadows, camera, cache, fromCache ) { + + if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) { + + // there's no local clipping + + if ( renderingShadows ) { + + // there's no global clipping + + projectPlanes( null ); + + } else { + + resetGlobalState(); + + } + + } else { + + var nGlobal = renderingShadows ? 0 : numGlobalPlanes, + lGlobal = nGlobal * 4, + + dstArray = cache.clippingState || null; + + uniform.value = dstArray; // ensure unique state + + dstArray = projectPlanes( planes, camera, lGlobal, fromCache ); + + for ( var i = 0; i !== lGlobal; ++ i ) { + + dstArray[ i ] = globalState[ i ]; + + } + + cache.clippingState = dstArray; + this.numIntersection = clipIntersection ? this.numPlanes : 0; + this.numPlanes += nGlobal; + + } + + + }; + + function resetGlobalState() { + + if ( uniform.value !== globalState ) { + + uniform.value = globalState; + uniform.needsUpdate = numGlobalPlanes > 0; + + } + + scope.numPlanes = numGlobalPlanes; + scope.numIntersection = 0; + + } + + function projectPlanes( planes, camera, dstOffset, skipTransform ) { + + var nPlanes = planes !== null ? planes.length : 0, + dstArray = null; + + if ( nPlanes !== 0 ) { + + dstArray = uniform.value; + + if ( skipTransform !== true || dstArray === null ) { + + var flatSize = dstOffset + nPlanes * 4, + viewMatrix = camera.matrixWorldInverse; + + viewNormalMatrix.getNormalMatrix( viewMatrix ); + + if ( dstArray === null || dstArray.length < flatSize ) { + + dstArray = new Float32Array( flatSize ); + + } + + for ( var i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) { + + plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix ); + + plane.normal.toArray( dstArray, i4 ); + dstArray[ i4 + 3 ] = plane.constant; + + } + + } + + uniform.value = dstArray; + uniform.needsUpdate = true; + + } + + scope.numPlanes = nPlanes; + + return dstArray; + + } + + } + + /** + * @author thespite / http://www.twitter.com/thespite + */ + + function WebGLUtils( gl, extensions ) { + + function convert( p ) { + + var extension; + + if ( p === RepeatWrapping ) return gl.REPEAT; + if ( p === ClampToEdgeWrapping ) return gl.CLAMP_TO_EDGE; + if ( p === MirroredRepeatWrapping ) return gl.MIRRORED_REPEAT; + + if ( p === NearestFilter ) return gl.NEAREST; + if ( p === NearestMipMapNearestFilter ) return gl.NEAREST_MIPMAP_NEAREST; + if ( p === NearestMipMapLinearFilter ) return gl.NEAREST_MIPMAP_LINEAR; + + if ( p === LinearFilter ) return gl.LINEAR; + if ( p === LinearMipMapNearestFilter ) return gl.LINEAR_MIPMAP_NEAREST; + if ( p === LinearMipMapLinearFilter ) return gl.LINEAR_MIPMAP_LINEAR; + + if ( p === UnsignedByteType ) return gl.UNSIGNED_BYTE; + if ( p === UnsignedShort4444Type ) return gl.UNSIGNED_SHORT_4_4_4_4; + if ( p === UnsignedShort5551Type ) return gl.UNSIGNED_SHORT_5_5_5_1; + if ( p === UnsignedShort565Type ) return gl.UNSIGNED_SHORT_5_6_5; + + if ( p === ByteType ) return gl.BYTE; + if ( p === ShortType ) return gl.SHORT; + if ( p === UnsignedShortType ) return gl.UNSIGNED_SHORT; + if ( p === IntType ) return gl.INT; + if ( p === UnsignedIntType ) return gl.UNSIGNED_INT; + if ( p === FloatType ) return gl.FLOAT; + + if ( p === HalfFloatType ) { + + extension = extensions.get( 'OES_texture_half_float' ); + + if ( extension !== null ) return extension.HALF_FLOAT_OES; + + } + + if ( p === AlphaFormat ) return gl.ALPHA; + if ( p === RGBFormat ) return gl.RGB; + if ( p === RGBAFormat ) return gl.RGBA; + if ( p === LuminanceFormat ) return gl.LUMINANCE; + if ( p === LuminanceAlphaFormat ) return gl.LUMINANCE_ALPHA; + if ( p === DepthFormat ) return gl.DEPTH_COMPONENT; + if ( p === DepthStencilFormat ) return gl.DEPTH_STENCIL; + + if ( p === AddEquation ) return gl.FUNC_ADD; + if ( p === SubtractEquation ) return gl.FUNC_SUBTRACT; + if ( p === ReverseSubtractEquation ) return gl.FUNC_REVERSE_SUBTRACT; + + if ( p === ZeroFactor ) return gl.ZERO; + if ( p === OneFactor ) return gl.ONE; + if ( p === SrcColorFactor ) return gl.SRC_COLOR; + if ( p === OneMinusSrcColorFactor ) return gl.ONE_MINUS_SRC_COLOR; + if ( p === SrcAlphaFactor ) return gl.SRC_ALPHA; + if ( p === OneMinusSrcAlphaFactor ) return gl.ONE_MINUS_SRC_ALPHA; + if ( p === DstAlphaFactor ) return gl.DST_ALPHA; + if ( p === OneMinusDstAlphaFactor ) return gl.ONE_MINUS_DST_ALPHA; + + if ( p === DstColorFactor ) return gl.DST_COLOR; + if ( p === OneMinusDstColorFactor ) return gl.ONE_MINUS_DST_COLOR; + if ( p === SrcAlphaSaturateFactor ) return gl.SRC_ALPHA_SATURATE; + + if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format || + p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); + + if ( extension !== null ) { + + if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; + if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; + if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; + + } + + } + + if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format || + p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + + if ( extension !== null ) { + + if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; + if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; + if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; + if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; + + } + + } + + if ( p === RGB_ETC1_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); + + if ( extension !== null ) return extension.COMPRESSED_RGB_ETC1_WEBGL; + + } + + if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format || + p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format || + p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format || + p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format || + p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ) { + + extension = extensions.get( 'WEBGL_compressed_texture_astc' ); + + if ( extension !== null ) { + + return p; + + } + + } + + if ( p === MinEquation || p === MaxEquation ) { + + extension = extensions.get( 'EXT_blend_minmax' ); + + if ( extension !== null ) { + + if ( p === MinEquation ) return extension.MIN_EXT; + if ( p === MaxEquation ) return extension.MAX_EXT; + + } + + } + + if ( p === UnsignedInt248Type ) { + + extension = extensions.get( 'WEBGL_depth_texture' ); + + if ( extension !== null ) return extension.UNSIGNED_INT_24_8_WEBGL; + + } + + return 0; + + } + + return { convert: convert }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function UniformsCache() { + + var lights = {}; + + return { + + get: function ( light ) { + + if ( lights[ light.id ] !== undefined ) { + + return lights[ light.id ]; + + } + + var uniforms; + + switch ( light.type ) { + + case 'DirectionalLight': + uniforms = { + direction: new Vector3(), + color: new Color(), + + shadow: false, + shadowBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; + + case 'SpotLight': + uniforms = { + position: new Vector3(), + direction: new Vector3(), + color: new Color(), + distance: 0, + coneCos: 0, + penumbraCos: 0, + decay: 0, + + shadow: false, + shadowBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2() + }; + break; + + case 'PointLight': + uniforms = { + position: new Vector3(), + color: new Color(), + distance: 0, + decay: 0, + + shadow: false, + shadowBias: 0, + shadowRadius: 1, + shadowMapSize: new Vector2(), + shadowCameraNear: 1, + shadowCameraFar: 1000 + }; + break; + + case 'HemisphereLight': + uniforms = { + direction: new Vector3(), + skyColor: new Color(), + groundColor: new Color() + }; + break; + + case 'RectAreaLight': + uniforms = { + color: new Color(), + position: new Vector3(), + halfWidth: new Vector3(), + halfHeight: new Vector3() + // TODO (abelnation): set RectAreaLight shadow uniforms + }; + break; + + } + + lights[ light.id ] = uniforms; + + return uniforms; + + } + + }; + + } + + var count = 0; + + function WebGLLights() { + + var cache = new UniformsCache(); + + var state = { + + id: count ++, + + hash: '', + + ambient: [ 0, 0, 0 ], + directional: [], + directionalShadowMap: [], + directionalShadowMatrix: [], + spot: [], + spotShadowMap: [], + spotShadowMatrix: [], + rectArea: [], + point: [], + pointShadowMap: [], + pointShadowMatrix: [], + hemi: [] + + }; + + var vector3 = new Vector3(); + var matrix4 = new Matrix4(); + var matrix42 = new Matrix4(); + + function setup( lights, shadows, camera ) { + + var r = 0, g = 0, b = 0; + + var directionalLength = 0; + var pointLength = 0; + var spotLength = 0; + var rectAreaLength = 0; + var hemiLength = 0; + + var viewMatrix = camera.matrixWorldInverse; + + for ( var i = 0, l = lights.length; i < l; i ++ ) { + + var light = lights[ i ]; + + var color = light.color; + var intensity = light.intensity; + var distance = light.distance; + + var shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null; + + if ( light.isAmbientLight ) { + + r += color.r * intensity; + g += color.g * intensity; + b += color.b * intensity; + + } else if ( light.isDirectionalLight ) { + + var uniforms = cache.get( light ); + + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); + + uniforms.shadow = light.castShadow; + + if ( light.castShadow ) { + + var shadow = light.shadow; + + uniforms.shadowBias = shadow.bias; + uniforms.shadowRadius = shadow.radius; + uniforms.shadowMapSize = shadow.mapSize; + + } + + state.directionalShadowMap[ directionalLength ] = shadowMap; + state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; + state.directional[ directionalLength ] = uniforms; + + directionalLength ++; + + } else if ( light.isSpotLight ) { + + var uniforms = cache.get( light ); + + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); + + uniforms.color.copy( color ).multiplyScalar( intensity ); + uniforms.distance = distance; + + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + vector3.setFromMatrixPosition( light.target.matrixWorld ); + uniforms.direction.sub( vector3 ); + uniforms.direction.transformDirection( viewMatrix ); + + uniforms.coneCos = Math.cos( light.angle ); + uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) ); + uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; + + uniforms.shadow = light.castShadow; + + if ( light.castShadow ) { + + var shadow = light.shadow; + + uniforms.shadowBias = shadow.bias; + uniforms.shadowRadius = shadow.radius; + uniforms.shadowMapSize = shadow.mapSize; + + } + + state.spotShadowMap[ spotLength ] = shadowMap; + state.spotShadowMatrix[ spotLength ] = light.shadow.matrix; + state.spot[ spotLength ] = uniforms; + + spotLength ++; + + } else if ( light.isRectAreaLight ) { + + var uniforms = cache.get( light ); + + // (a) intensity is the total visible light emitted + //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) ); + + // (b) intensity is the brightness of the light + uniforms.color.copy( color ).multiplyScalar( intensity ); + + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); + + // extract local rotation of light to derive width/height half vectors + matrix42.identity(); + matrix4.copy( light.matrixWorld ); + matrix4.premultiply( viewMatrix ); + matrix42.extractRotation( matrix4 ); + + uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 ); + uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 ); + + uniforms.halfWidth.applyMatrix4( matrix42 ); + uniforms.halfHeight.applyMatrix4( matrix42 ); + + // TODO (abelnation): RectAreaLight distance? + // uniforms.distance = distance; + + state.rectArea[ rectAreaLength ] = uniforms; + + rectAreaLength ++; + + } else if ( light.isPointLight ) { + + var uniforms = cache.get( light ); + + uniforms.position.setFromMatrixPosition( light.matrixWorld ); + uniforms.position.applyMatrix4( viewMatrix ); + + uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); + uniforms.distance = light.distance; + uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; + + uniforms.shadow = light.castShadow; + + if ( light.castShadow ) { + + var shadow = light.shadow; + + uniforms.shadowBias = shadow.bias; + uniforms.shadowRadius = shadow.radius; + uniforms.shadowMapSize = shadow.mapSize; + uniforms.shadowCameraNear = shadow.camera.near; + uniforms.shadowCameraFar = shadow.camera.far; + + } + + state.pointShadowMap[ pointLength ] = shadowMap; + state.pointShadowMatrix[ pointLength ] = light.shadow.matrix; + state.point[ pointLength ] = uniforms; + + pointLength ++; + + } else if ( light.isHemisphereLight ) { + + var uniforms = cache.get( light ); + + uniforms.direction.setFromMatrixPosition( light.matrixWorld ); + uniforms.direction.transformDirection( viewMatrix ); + uniforms.direction.normalize(); + + uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); + uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); + + state.hemi[ hemiLength ] = uniforms; + + hemiLength ++; + + } + + } + + state.ambient[ 0 ] = r; + state.ambient[ 1 ] = g; + state.ambient[ 2 ] = b; + + state.directional.length = directionalLength; + state.spot.length = spotLength; + state.rectArea.length = rectAreaLength; + state.point.length = pointLength; + state.hemi.length = hemiLength; + + state.hash = state.id + ',' + directionalLength + ',' + pointLength + ',' + spotLength + ',' + rectAreaLength + ',' + hemiLength + ',' + shadows.length; + + } + + return { + setup: setup, + state: state + }; + + } + + /** + * @author Mugen87 / https://github.com/Mugen87 + */ + + function WebGLRenderState() { + + var lights = new WebGLLights(); + + var lightsArray = []; + var shadowsArray = []; + var spritesArray = []; + + function init() { + + lightsArray.length = 0; + shadowsArray.length = 0; + spritesArray.length = 0; + + } + + function pushLight( light ) { + + lightsArray.push( light ); + + } + + function pushShadow( shadowLight ) { + + shadowsArray.push( shadowLight ); + + } + + function pushSprite( shadowLight ) { + + spritesArray.push( shadowLight ); + + } + + function setupLights( camera ) { + + lights.setup( lightsArray, shadowsArray, camera ); + + } + + var state = { + lightsArray: lightsArray, + shadowsArray: shadowsArray, + spritesArray: spritesArray, + + lights: lights + }; + + return { + init: init, + state: state, + setupLights: setupLights, + + pushLight: pushLight, + pushShadow: pushShadow, + pushSprite: pushSprite + }; + + } + + function WebGLRenderStates() { + + var renderStates = {}; + + function get( scene, camera ) { + + var hash = scene.id + ',' + camera.id; + + var renderState = renderStates[ hash ]; + + if ( renderState === undefined ) { + + renderState = new WebGLRenderState(); + renderStates[ hash ] = renderState; + + } + + return renderState; + + } + + function dispose() { + + renderStates = {}; + + } + + return { + get: get, + dispose: dispose + }; + + } + + /** + * @author supereggbert / http://www.paulbrunt.co.uk/ + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * @author szimek / https://github.com/szimek/ + * @author tschw + */ + + function WebGLRenderer( parameters ) { + + //console.log( 'THREE.WebGLRenderer', REVISION ); + + parameters = parameters || {}; + + var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ), + _context = parameters.context !== undefined ? parameters.context : null, + + _alpha = parameters.alpha !== undefined ? parameters.alpha : false, + _depth = parameters.depth !== undefined ? parameters.depth : true, + _stencil = parameters.stencil !== undefined ? parameters.stencil : true, + _antialias = parameters.antialias !== undefined ? parameters.antialias : false, + _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, + _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false, + _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default'; + + var currentRenderList = null; + var currentRenderState = null; + + // public properties + + this.domElement = _canvas; + this.context = null; + + // clearing + + this.autoClear = true; + this.autoClearColor = true; + this.autoClearDepth = true; + this.autoClearStencil = true; + + // scene graph + + this.sortObjects = true; + + // user-defined clipping + + this.clippingPlanes = []; + this.localClippingEnabled = false; + + // physically based shading + + this.gammaFactor = 2.0; // for backwards compatibility + this.gammaInput = false; + this.gammaOutput = false; + + // physical lights + + this.physicallyCorrectLights = false; + + // tone mapping + + this.toneMapping = LinearToneMapping; + this.toneMappingExposure = 1.0; + this.toneMappingWhitePoint = 1.0; + + // morphs + + this.maxMorphTargets = 8; + this.maxMorphNormals = 4; + + // internal properties + + var _this = this, + + _isContextLost = false, + + // internal state cache + + _currentRenderTarget = null, + _currentFramebuffer = null, + _currentMaterialId = - 1, + _currentGeometryProgram = '', + + _currentCamera = null, + _currentArrayCamera = null, + + _currentViewport = new Vector4(), + _currentScissor = new Vector4(), + _currentScissorTest = null, + + // + + _usedTextureUnits = 0, + + // + + _width = _canvas.width, + _height = _canvas.height, + + _pixelRatio = 1, + + _viewport = new Vector4( 0, 0, _width, _height ), + _scissor = new Vector4( 0, 0, _width, _height ), + _scissorTest = false, + + // frustum + + _frustum = new Frustum(), + + // clipping + + _clipping = new WebGLClipping(), + _clippingEnabled = false, + _localClippingEnabled = false, + + // camera matrices cache + + _projScreenMatrix = new Matrix4(), + + _vector3 = new Vector3(), + + // info + + _infoMemory = { + geometries: 0, + textures: 0 + }, + + _infoRender = { + + frame: 0, + calls: 0, + vertices: 0, + faces: 0, + points: 0 + + }; + + this.info = { + + render: _infoRender, + memory: _infoMemory, + programs: null, + autoReset: true, + reset: resetInfo + + }; + + function resetInfo() { + + _infoRender.frame ++; + _infoRender.calls = 0; + _infoRender.vertices = 0; + _infoRender.faces = 0; + _infoRender.points = 0; + + } + + function getTargetPixelRatio() { + + return _currentRenderTarget === null ? _pixelRatio : 1; + + } + + // initialize + + var _gl; + + try { + + var contextAttributes = { + alpha: _alpha, + depth: _depth, + stencil: _stencil, + antialias: _antialias, + premultipliedAlpha: _premultipliedAlpha, + preserveDrawingBuffer: _preserveDrawingBuffer, + powerPreference: _powerPreference + }; + + // event listeners must be registered before WebGL context is created, see #12753 + + _canvas.addEventListener( 'webglcontextlost', onContextLost, false ); + _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); + + _gl = _context || _canvas.getContext( 'webgl', contextAttributes ) || _canvas.getContext( 'experimental-webgl', contextAttributes ); + + if ( _gl === null ) { + + if ( _canvas.getContext( 'webgl' ) !== null ) { + + throw new Error( 'Error creating WebGL context with your selected attributes.' ); + + } else { + + throw new Error( 'Error creating WebGL context.' ); + + } + + } + + // Some experimental-webgl implementations do not have getShaderPrecisionFormat + + if ( _gl.getShaderPrecisionFormat === undefined ) { + + _gl.getShaderPrecisionFormat = function () { + + return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; + + }; + + } + + } catch ( error ) { + + console.error( 'THREE.WebGLRenderer: ' + error.message ); + + } + + var extensions, capabilities, state; + var properties, textures, attributes, geometries, objects; + var programCache, renderLists, renderStates; + + var background, morphtargets, bufferRenderer, indexedBufferRenderer; + var spriteRenderer; + + var utils; + + function initGLContext() { + + extensions = new WebGLExtensions( _gl ); + extensions.get( 'WEBGL_depth_texture' ); + extensions.get( 'OES_texture_float' ); + extensions.get( 'OES_texture_float_linear' ); + extensions.get( 'OES_texture_half_float' ); + extensions.get( 'OES_texture_half_float_linear' ); + extensions.get( 'OES_standard_derivatives' ); + extensions.get( 'OES_element_index_uint' ); + extensions.get( 'ANGLE_instanced_arrays' ); + + utils = new WebGLUtils( _gl, extensions ); + + capabilities = new WebGLCapabilities( _gl, extensions, parameters ); + + state = new WebGLState( _gl, extensions, utils ); + state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) ); + state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) ); + + properties = new WebGLProperties(); + textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, _infoMemory, _infoRender ); + attributes = new WebGLAttributes( _gl ); + geometries = new WebGLGeometries( _gl, attributes, _infoMemory ); + objects = new WebGLObjects( geometries, _infoRender ); + morphtargets = new WebGLMorphtargets( _gl ); + programCache = new WebGLPrograms( _this, extensions, capabilities ); + renderLists = new WebGLRenderLists(); + renderStates = new WebGLRenderStates(); + + background = new WebGLBackground( _this, state, geometries, _premultipliedAlpha ); + + bufferRenderer = new WebGLBufferRenderer( _gl, extensions, _infoRender ); + indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, _infoRender ); + + spriteRenderer = new WebGLSpriteRenderer( _this, _gl, state, textures, capabilities ); + + _this.info.programs = programCache.programs; + + _this.context = _gl; + _this.capabilities = capabilities; + _this.extensions = extensions; + _this.properties = properties; + _this.renderLists = renderLists; + _this.state = state; + + } + + initGLContext(); + + // vr + + var vr = new WebVRManager( _this ); + + this.vr = vr; + + // shadow map + + var shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize ); + + this.shadowMap = shadowMap; + + // API + + this.getContext = function () { + + return _gl; + + }; + + this.getContextAttributes = function () { + + return _gl.getContextAttributes(); + + }; + + this.forceContextLoss = function () { + + var extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.loseContext(); + + }; + + this.forceContextRestore = function () { + + var extension = extensions.get( 'WEBGL_lose_context' ); + if ( extension ) extension.restoreContext(); + + }; + + this.getPixelRatio = function () { + + return _pixelRatio; + + }; + + this.setPixelRatio = function ( value ) { + + if ( value === undefined ) return; + + _pixelRatio = value; + + this.setSize( _width, _height, false ); + + }; + + this.getSize = function () { + + return { + width: _width, + height: _height + }; + + }; + + this.setSize = function ( width, height, updateStyle ) { + + var device = vr.getDevice(); + + if ( device && device.isPresenting ) { + + console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); + return; + + } + + _width = width; + _height = height; + + _canvas.width = width * _pixelRatio; + _canvas.height = height * _pixelRatio; + + if ( updateStyle !== false ) { + + _canvas.style.width = width + 'px'; + _canvas.style.height = height + 'px'; + + } + + this.setViewport( 0, 0, width, height ); + + }; + + this.getDrawingBufferSize = function () { + + return { + width: _width * _pixelRatio, + height: _height * _pixelRatio + }; + + }; + + this.setDrawingBufferSize = function ( width, height, pixelRatio ) { + + _width = width; + _height = height; + + _pixelRatio = pixelRatio; + + _canvas.width = width * pixelRatio; + _canvas.height = height * pixelRatio; + + this.setViewport( 0, 0, width, height ); + + }; + + this.getCurrentViewport = function () { + + return _currentViewport; + + }; + + this.setViewport = function ( x, y, width, height ) { + + _viewport.set( x, _height - y - height, width, height ); + state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) ); + + }; + + this.setScissor = function ( x, y, width, height ) { + + _scissor.set( x, _height - y - height, width, height ); + state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) ); + + }; + + this.setScissorTest = function ( boolean ) { + + state.setScissorTest( _scissorTest = boolean ); + + }; + + // Clearing + + this.getClearColor = function () { + + return background.getClearColor(); + + }; + + this.setClearColor = function () { + + background.setClearColor.apply( background, arguments ); + + }; + + this.getClearAlpha = function () { + + return background.getClearAlpha(); + + }; + + this.setClearAlpha = function () { + + background.setClearAlpha.apply( background, arguments ); + + }; + + this.clear = function ( color, depth, stencil ) { + + var bits = 0; + + if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT; + if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT; + if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT; + + _gl.clear( bits ); + + }; + + this.clearColor = function () { + + this.clear( true, false, false ); + + }; + + this.clearDepth = function () { + + this.clear( false, true, false ); + + }; + + this.clearStencil = function () { + + this.clear( false, false, true ); + + }; + + this.clearTarget = function ( renderTarget, color, depth, stencil ) { + + this.setRenderTarget( renderTarget ); + this.clear( color, depth, stencil ); + + }; + + // + + this.dispose = function () { + + _canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); + _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); + + renderLists.dispose(); + renderStates.dispose(); + properties.dispose(); + objects.dispose(); + + vr.dispose(); + + stopAnimation(); + + }; + + // Events + + function onContextLost( event ) { + + event.preventDefault(); + + console.log( 'THREE.WebGLRenderer: Context Lost.' ); + + _isContextLost = true; + + } + + function onContextRestore( /* event */ ) { + + console.log( 'THREE.WebGLRenderer: Context Restored.' ); + + _isContextLost = false; + + initGLContext(); + + } + + function onMaterialDispose( event ) { + + var material = event.target; + + material.removeEventListener( 'dispose', onMaterialDispose ); + + deallocateMaterial( material ); + + } + + // Buffer deallocation + + function deallocateMaterial( material ) { + + releaseMaterialProgramReference( material ); + + properties.remove( material ); + + } + + + function releaseMaterialProgramReference( material ) { + + var programInfo = properties.get( material ).program; + + material.program = undefined; + + if ( programInfo !== undefined ) { + + programCache.releaseProgram( programInfo ); + + } + + } + + // Buffer rendering + + function renderObjectImmediate( object, program, material ) { + + object.render( function ( object ) { + + _this.renderBufferImmediate( object, program, material ); + + } ); + + } + + this.renderBufferImmediate = function ( object, program, material ) { + + state.initAttributes(); + + var buffers = properties.get( object ); + + if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer(); + if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer(); + if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer(); + if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer(); + + var programAttributes = program.getAttributes(); + + if ( object.hasPositions ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); + + state.enableAttribute( programAttributes.position ); + _gl.vertexAttribPointer( programAttributes.position, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasNormals ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal ); + + if ( ! material.isMeshPhongMaterial && + ! material.isMeshStandardMaterial && + ! material.isMeshNormalMaterial && + material.flatShading === true ) { + + for ( var i = 0, l = object.count * 3; i < l; i += 9 ) { + + var array = object.normalArray; + + var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3; + var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3; + var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3; + + array[ i + 0 ] = nx; + array[ i + 1 ] = ny; + array[ i + 2 ] = nz; + + array[ i + 3 ] = nx; + array[ i + 4 ] = ny; + array[ i + 5 ] = nz; + + array[ i + 6 ] = nx; + array[ i + 7 ] = ny; + array[ i + 8 ] = nz; + + } + + } + + _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); + + state.enableAttribute( programAttributes.normal ); + + _gl.vertexAttribPointer( programAttributes.normal, 3, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasUvs && material.map ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); + + state.enableAttribute( programAttributes.uv ); + + _gl.vertexAttribPointer( programAttributes.uv, 2, _gl.FLOAT, false, 0, 0 ); + + } + + if ( object.hasColors && material.vertexColors !== NoColors ) { + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color ); + _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); + + state.enableAttribute( programAttributes.color ); + + _gl.vertexAttribPointer( programAttributes.color, 3, _gl.FLOAT, false, 0, 0 ); + + } + + state.disableUnusedAttributes(); + + _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); + + object.count = 0; + + }; + + this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) { + + var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + + state.setMaterial( material, frontFaceCW ); + + var program = setProgram( camera, fog, material, object ); + var geometryProgram = geometry.id + '_' + program.id + '_' + ( material.wireframe === true ); + + var updateBuffers = false; + + if ( geometryProgram !== _currentGeometryProgram ) { + + _currentGeometryProgram = geometryProgram; + updateBuffers = true; + + } + + if ( object.morphTargetInfluences ) { + + morphtargets.update( object, geometry, material, program ); + + updateBuffers = true; + + } + + // + + var index = geometry.index; + var position = geometry.attributes.position; + var rangeFactor = 1; + + if ( material.wireframe === true ) { + + index = geometries.getWireframeAttribute( geometry ); + rangeFactor = 2; + + } + + var attribute; + var renderer = bufferRenderer; + + if ( index !== null ) { + + attribute = attributes.get( index ); + + renderer = indexedBufferRenderer; + renderer.setIndex( attribute ); + + } + + if ( updateBuffers ) { + + setupVertexAttributes( material, program, geometry ); + + if ( index !== null ) { + + _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attribute.buffer ); + + } + + } + + // + + var dataCount = Infinity; + + if ( index !== null ) { + + dataCount = index.count; + + } else if ( position !== undefined ) { + + dataCount = position.count; + + } + + var rangeStart = geometry.drawRange.start * rangeFactor; + var rangeCount = geometry.drawRange.count * rangeFactor; + + var groupStart = group !== null ? group.start * rangeFactor : 0; + var groupCount = group !== null ? group.count * rangeFactor : Infinity; + + var drawStart = Math.max( rangeStart, groupStart ); + var drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1; + + var drawCount = Math.max( 0, drawEnd - drawStart + 1 ); + + if ( drawCount === 0 ) return; + + // + + if ( object.isMesh ) { + + if ( material.wireframe === true ) { + + state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); + renderer.setMode( _gl.LINES ); + + } else { + + switch ( object.drawMode ) { + + case TrianglesDrawMode: + renderer.setMode( _gl.TRIANGLES ); + break; + + case TriangleStripDrawMode: + renderer.setMode( _gl.TRIANGLE_STRIP ); + break; + + case TriangleFanDrawMode: + renderer.setMode( _gl.TRIANGLE_FAN ); + break; + + } + + } + + + } else if ( object.isLine ) { + + var lineWidth = material.linewidth; + + if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material + + state.setLineWidth( lineWidth * getTargetPixelRatio() ); + + if ( object.isLineSegments ) { + + renderer.setMode( _gl.LINES ); + + } else if ( object.isLineLoop ) { + + renderer.setMode( _gl.LINE_LOOP ); + + } else { + + renderer.setMode( _gl.LINE_STRIP ); + + } + + } else if ( object.isPoints ) { + + renderer.setMode( _gl.POINTS ); + + } + + if ( geometry && geometry.isInstancedBufferGeometry ) { + + if ( geometry.maxInstancedCount > 0 ) { + + renderer.renderInstances( geometry, drawStart, drawCount ); + + } + + } else { + + renderer.render( drawStart, drawCount ); + + } + + }; + + function setupVertexAttributes( material, program, geometry, startIndex ) { + + if ( geometry && geometry.isInstancedBufferGeometry ) { + + if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) { + + console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); + return; + + } + + } + + if ( startIndex === undefined ) startIndex = 0; + + state.initAttributes(); + + var geometryAttributes = geometry.attributes; + + var programAttributes = program.getAttributes(); + + var materialDefaultAttributeValues = material.defaultAttributeValues; + + for ( var name in programAttributes ) { + + var programAttribute = programAttributes[ name ]; + + if ( programAttribute >= 0 ) { + + var geometryAttribute = geometryAttributes[ name ]; + + if ( geometryAttribute !== undefined ) { + + var normalized = geometryAttribute.normalized; + var size = geometryAttribute.itemSize; + + var attribute = attributes.get( geometryAttribute ); + + // TODO Attribute may not be available on context restore + + if ( attribute === undefined ) continue; + + var buffer = attribute.buffer; + var type = attribute.type; + var bytesPerElement = attribute.bytesPerElement; + + if ( geometryAttribute.isInterleavedBufferAttribute ) { + + var data = geometryAttribute.data; + var stride = data.stride; + var offset = geometryAttribute.offset; + + if ( data && data.isInstancedInterleavedBuffer ) { + + state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute ); + + if ( geometry.maxInstancedCount === undefined ) { + + geometry.maxInstancedCount = data.meshPerAttribute * data.count; + + } + + } else { + + state.enableAttribute( programAttribute ); + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); + _gl.vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, ( startIndex * stride + offset ) * bytesPerElement ); + + } else { + + if ( geometryAttribute.isInstancedBufferAttribute ) { + + state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute ); + + if ( geometry.maxInstancedCount === undefined ) { + + geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; + + } + + } else { + + state.enableAttribute( programAttribute ); + + } + + _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); + _gl.vertexAttribPointer( programAttribute, size, type, normalized, 0, startIndex * size * bytesPerElement ); + + } + + } else if ( materialDefaultAttributeValues !== undefined ) { + + var value = materialDefaultAttributeValues[ name ]; + + if ( value !== undefined ) { + + switch ( value.length ) { + + case 2: + _gl.vertexAttrib2fv( programAttribute, value ); + break; + + case 3: + _gl.vertexAttrib3fv( programAttribute, value ); + break; + + case 4: + _gl.vertexAttrib4fv( programAttribute, value ); + break; + + default: + _gl.vertexAttrib1fv( programAttribute, value ); + + } + + } + + } + + } + + } + + state.disableUnusedAttributes(); + + } + + // Compile + + this.compile = function ( scene, camera ) { + + currentRenderState = renderStates.get( scene, camera ); + currentRenderState.init(); + + scene.traverse( function ( object ) { + + if ( object.isLight ) { + + currentRenderState.pushLight( object ); + + if ( object.castShadow ) { + + currentRenderState.pushShadow( object ); + + } + + } + + } ); + + currentRenderState.setupLights( camera ); + + scene.traverse( function ( object ) { + + if ( object.material ) { + + if ( Array.isArray( object.material ) ) { + + for ( var i = 0; i < object.material.length; i ++ ) { + + initMaterial( object.material[ i ], scene.fog, object ); + + } + + } else { + + initMaterial( object.material, scene.fog, object ); + + } + + } + + } ); + + }; + + // Animation Loop + + var isAnimating = false; + var onAnimationFrame = null; + + function startAnimation() { + + if ( isAnimating ) return; + + requestAnimationLoopFrame(); + + isAnimating = true; + + } + + function stopAnimation() { + + isAnimating = false; + + } + + function requestAnimationLoopFrame() { + + var device = vr.getDevice(); + + if ( device && device.isPresenting ) { + + device.requestAnimationFrame( animationLoop ); + + } else { + + window.requestAnimationFrame( animationLoop ); + + } + + } + + function animationLoop( time ) { + + if ( isAnimating === false ) return; + + onAnimationFrame( time ); + + requestAnimationLoopFrame(); + + } + + this.animate = function ( callback ) { + + onAnimationFrame = callback; + onAnimationFrame !== null ? startAnimation() : stopAnimation(); + + }; + + // Rendering + + this.render = function ( scene, camera, renderTarget, forceClear ) { + + if ( ! ( camera && camera.isCamera ) ) { + + console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); + return; + + } + + if ( _isContextLost ) return; + + // reset caching for this frame + + _currentGeometryProgram = ''; + _currentMaterialId = - 1; + _currentCamera = null; + + // update scene graph + + if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); + + // update camera matrices and frustum + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + if ( vr.enabled ) { + + camera = vr.getCamera( camera ); + + } + + // + + currentRenderState = renderStates.get( scene, camera ); + currentRenderState.init(); + + scene.onBeforeRender( _this, scene, camera, renderTarget ); + + _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); + _frustum.setFromMatrix( _projScreenMatrix ); + + _localClippingEnabled = this.localClippingEnabled; + _clippingEnabled = _clipping.init( this.clippingPlanes, _localClippingEnabled, camera ); + + currentRenderList = renderLists.get( scene, camera ); + currentRenderList.init(); + + projectObject( scene, camera, _this.sortObjects ); + + if ( _this.sortObjects === true ) { + + currentRenderList.sort(); + + } + + // + + if ( _clippingEnabled ) _clipping.beginShadows(); + + var shadowsArray = currentRenderState.state.shadowsArray; + + shadowMap.render( shadowsArray, scene, camera ); + + currentRenderState.setupLights( camera ); + + if ( _clippingEnabled ) _clipping.endShadows(); + + // + + if ( this.info.autoReset ) this.info.reset(); + + if ( renderTarget === undefined ) { + + renderTarget = null; + + } + + this.setRenderTarget( renderTarget ); + + // + + background.render( currentRenderList, scene, camera, forceClear ); + + // render scene + + var opaqueObjects = currentRenderList.opaque; + var transparentObjects = currentRenderList.transparent; + + if ( scene.overrideMaterial ) { + + var overrideMaterial = scene.overrideMaterial; + + if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera, overrideMaterial ); + if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera, overrideMaterial ); + + } else { + + // opaque pass (front-to-back order) + + if ( opaqueObjects.length ) renderObjects( opaqueObjects, scene, camera ); + + // transparent pass (back-to-front order) + + if ( transparentObjects.length ) renderObjects( transparentObjects, scene, camera ); + + } + + // custom renderers + + var spritesArray = currentRenderState.state.spritesArray; + + spriteRenderer.render( spritesArray, scene, camera ); + + // Generate mipmap if we're using any kind of mipmap filtering + + if ( renderTarget ) { + + textures.updateRenderTargetMipmap( renderTarget ); + + } + + // Ensure depth buffer writing is enabled so it can be cleared on next render + + state.buffers.depth.setTest( true ); + state.buffers.depth.setMask( true ); + state.buffers.color.setMask( true ); + + state.setPolygonOffset( false ); + + scene.onAfterRender( _this, scene, camera ); + + if ( vr.enabled ) { + + vr.submitFrame(); + + } + + // _gl.finish(); + + currentRenderList = null; + currentRenderState = null; + + }; + + /* + // TODO Duplicated code (Frustum) + + var _sphere = new Sphere(); + + function isObjectViewable( object ) { + + var geometry = object.geometry; + + if ( geometry.boundingSphere === null ) + geometry.computeBoundingSphere(); + + _sphere.copy( geometry.boundingSphere ). + applyMatrix4( object.matrixWorld ); + + return isSphereViewable( _sphere ); + + } + + function isSpriteViewable( sprite ) { + + _sphere.center.set( 0, 0, 0 ); + _sphere.radius = 0.7071067811865476; + _sphere.applyMatrix4( sprite.matrixWorld ); + + return isSphereViewable( _sphere ); + + } + + function isSphereViewable( sphere ) { + + if ( ! _frustum.intersectsSphere( sphere ) ) return false; + + var numPlanes = _clipping.numPlanes; + + if ( numPlanes === 0 ) return true; + + var planes = _this.clippingPlanes, + + center = sphere.center, + negRad = - sphere.radius, + i = 0; + + do { + + // out when deeper than radius in the negative halfspace + if ( planes[ i ].distanceToPoint( center ) < negRad ) return false; + + } while ( ++ i !== numPlanes ); + + return true; + + } + */ + + function projectObject( object, camera, sortObjects ) { + + if ( object.visible === false ) return; + + var visible = object.layers.test( camera.layers ); + + if ( visible ) { + + if ( object.isLight ) { + + currentRenderState.pushLight( object ); + + if ( object.castShadow ) { + + currentRenderState.pushShadow( object ); + + } + + } else if ( object.isSprite ) { + + if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { + + currentRenderState.pushSprite( object ); + + } + + } else if ( object.isImmediateRenderObject ) { + + if ( sortObjects ) { + + _vector3.setFromMatrixPosition( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); + + } + + currentRenderList.push( object, null, object.material, _vector3.z, null ); + + } else if ( object.isMesh || object.isLine || object.isPoints ) { + + if ( object.isSkinnedMesh ) { + + object.skeleton.update(); + + } + + if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { + + if ( sortObjects ) { + + _vector3.setFromMatrixPosition( object.matrixWorld ) + .applyMatrix4( _projScreenMatrix ); + + } + + var geometry = objects.update( object ); + var material = object.material; + + if ( Array.isArray( material ) ) { + + var groups = geometry.groups; + + for ( var i = 0, l = groups.length; i < l; i ++ ) { + + var group = groups[ i ]; + var groupMaterial = material[ group.materialIndex ]; + + if ( groupMaterial && groupMaterial.visible ) { + + currentRenderList.push( object, geometry, groupMaterial, _vector3.z, group ); + + } + + } + + } else if ( material.visible ) { + + currentRenderList.push( object, geometry, material, _vector3.z, null ); + + } + + } + + } + + } + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + projectObject( children[ i ], camera, sortObjects ); + + } + + } + + function renderObjects( renderList, scene, camera, overrideMaterial ) { + + for ( var i = 0, l = renderList.length; i < l; i ++ ) { + + var renderItem = renderList[ i ]; + + var object = renderItem.object; + var geometry = renderItem.geometry; + var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; + var group = renderItem.group; + + if ( camera.isArrayCamera ) { + + _currentArrayCamera = camera; + + var cameras = camera.cameras; + + for ( var j = 0, jl = cameras.length; j < jl; j ++ ) { + + var camera2 = cameras[ j ]; + + if ( object.layers.test( camera2.layers ) ) { + + var bounds = camera2.bounds; + + var x = bounds.x * _width; + var y = bounds.y * _height; + var width = bounds.z * _width; + var height = bounds.w * _height; + + state.viewport( _currentViewport.set( x, y, width, height ).multiplyScalar( _pixelRatio ) ); + + renderObject( object, scene, camera2, geometry, material, group ); + + } + + } + + } else { + + _currentArrayCamera = null; + + renderObject( object, scene, camera, geometry, material, group ); + + } + + } + + } + + function renderObject( object, scene, camera, geometry, material, group ) { + + object.onBeforeRender( _this, scene, camera, geometry, material, group ); + currentRenderState = renderStates.get( scene, _currentArrayCamera || camera ); + + object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); + object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); + + if ( object.isImmediateRenderObject ) { + + var frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); + + state.setMaterial( material, frontFaceCW ); + + var program = setProgram( camera, scene.fog, material, object ); + + _currentGeometryProgram = ''; + + renderObjectImmediate( object, program, material ); + + } else { + + _this.renderBufferDirect( camera, scene.fog, geometry, material, object, group ); + + } + + object.onAfterRender( _this, scene, camera, geometry, material, group ); + currentRenderState = renderStates.get( scene, _currentArrayCamera || camera ); + + } + + function initMaterial( material, fog, object ) { + + var materialProperties = properties.get( material ); + + var lights = currentRenderState.state.lights; + var shadowsArray = currentRenderState.state.shadowsArray; + + var parameters = programCache.getParameters( + material, lights.state, shadowsArray, fog, _clipping.numPlanes, _clipping.numIntersection, object ); + + var code = programCache.getProgramCode( material, parameters ); + + var program = materialProperties.program; + var programChange = true; + + if ( program === undefined ) { + + // new material + material.addEventListener( 'dispose', onMaterialDispose ); + + } else if ( program.code !== code ) { + + // changed glsl or parameters + releaseMaterialProgramReference( material ); + + } else if ( materialProperties.lightsHash !== lights.state.hash ) { + + properties.update( material, 'lightsHash', lights.state.hash ); + programChange = false; + + } else if ( parameters.shaderID !== undefined ) { + + // same glsl and uniform list + return; + + } else { + + // only rebuild uniform list + programChange = false; + + } + + if ( programChange ) { + + if ( parameters.shaderID ) { + + var shader = ShaderLib[ parameters.shaderID ]; + + materialProperties.shader = { + name: material.type, + uniforms: UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader + }; + + } else { + + materialProperties.shader = { + name: material.type, + uniforms: material.uniforms, + vertexShader: material.vertexShader, + fragmentShader: material.fragmentShader + }; + + } + + material.onBeforeCompile( materialProperties.shader ); + + program = programCache.acquireProgram( material, materialProperties.shader, parameters, code ); + + materialProperties.program = program; + material.program = program; + + } + + var programAttributes = program.getAttributes(); + + if ( material.morphTargets ) { + + material.numSupportedMorphTargets = 0; + + for ( var i = 0; i < _this.maxMorphTargets; i ++ ) { + + if ( programAttributes[ 'morphTarget' + i ] >= 0 ) { + + material.numSupportedMorphTargets ++; + + } + + } + + } + + if ( material.morphNormals ) { + + material.numSupportedMorphNormals = 0; + + for ( var i = 0; i < _this.maxMorphNormals; i ++ ) { + + if ( programAttributes[ 'morphNormal' + i ] >= 0 ) { + + material.numSupportedMorphNormals ++; + + } + + } + + } + + var uniforms = materialProperties.shader.uniforms; + + if ( ! material.isShaderMaterial && + ! material.isRawShaderMaterial || + material.clipping === true ) { + + materialProperties.numClippingPlanes = _clipping.numPlanes; + materialProperties.numIntersection = _clipping.numIntersection; + uniforms.clippingPlanes = _clipping.uniform; + + } + + materialProperties.fog = fog; + + // store the light setup it was created for + + materialProperties.lightsHash = lights.state.hash; + + if ( material.lights ) { + + // wire up the material to this renderer's lighting state + + uniforms.ambientLightColor.value = lights.state.ambient; + uniforms.directionalLights.value = lights.state.directional; + uniforms.spotLights.value = lights.state.spot; + uniforms.rectAreaLights.value = lights.state.rectArea; + uniforms.pointLights.value = lights.state.point; + uniforms.hemisphereLights.value = lights.state.hemi; + + uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; + uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; + uniforms.spotShadowMap.value = lights.state.spotShadowMap; + uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix; + uniforms.pointShadowMap.value = lights.state.pointShadowMap; + uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; + // TODO (abelnation): add area lights shadow info to uniforms + + } + + var progUniforms = materialProperties.program.getUniforms(), + uniformsList = + WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); + + materialProperties.uniformsList = uniformsList; + + } + + function setProgram( camera, fog, material, object ) { + + _usedTextureUnits = 0; + + var materialProperties = properties.get( material ); + var lights = currentRenderState.state.lights; + + if ( _clippingEnabled ) { + + if ( _localClippingEnabled || camera !== _currentCamera ) { + + var useCache = + camera === _currentCamera && + material.id === _currentMaterialId; + + // we might want to call this function with some ClippingGroup + // object instead of the material, once it becomes feasible + // (#8465, #8379) + _clipping.setState( + material.clippingPlanes, material.clipIntersection, material.clipShadows, + camera, materialProperties, useCache ); + + } + + } + + if ( material.needsUpdate === false ) { + + if ( materialProperties.program === undefined ) { + + material.needsUpdate = true; + + } else if ( material.fog && materialProperties.fog !== fog ) { + + material.needsUpdate = true; + + } else if ( material.lights && materialProperties.lightsHash !== lights.state.hash ) { + + material.needsUpdate = true; + + } else if ( materialProperties.numClippingPlanes !== undefined && + ( materialProperties.numClippingPlanes !== _clipping.numPlanes || + materialProperties.numIntersection !== _clipping.numIntersection ) ) { + + material.needsUpdate = true; + + } + + } + + if ( material.needsUpdate ) { + + initMaterial( material, fog, object ); + material.needsUpdate = false; + + } + + var refreshProgram = false; + var refreshMaterial = false; + var refreshLights = false; + + var program = materialProperties.program, + p_uniforms = program.getUniforms(), + m_uniforms = materialProperties.shader.uniforms; + + if ( state.useProgram( program.program ) ) { + + refreshProgram = true; + refreshMaterial = true; + refreshLights = true; + + } + + if ( material.id !== _currentMaterialId ) { + + _currentMaterialId = material.id; + + refreshMaterial = true; + + } + + if ( refreshProgram || camera !== _currentCamera ) { + + p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); + + if ( capabilities.logarithmicDepthBuffer ) { + + p_uniforms.setValue( _gl, 'logDepthBufFC', + 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); + + } + + // Avoid unneeded uniform updates per ArrayCamera's sub-camera + + if ( _currentCamera !== ( _currentArrayCamera || camera ) ) { + + _currentCamera = ( _currentArrayCamera || camera ); + + // lighting uniforms depend on the camera so enforce an update + // now, in case this material supports lights - or later, when + // the next material that does gets activated: + + refreshMaterial = true; // set to true on material change + refreshLights = true; // remains set until update done + + } + + // load material specific uniforms + // (shader material also gets them for the sake of genericity) + + if ( material.isShaderMaterial || + material.isMeshPhongMaterial || + material.isMeshStandardMaterial || + material.envMap ) { + + var uCamPos = p_uniforms.map.cameraPosition; + + if ( uCamPos !== undefined ) { + + uCamPos.setValue( _gl, + _vector3.setFromMatrixPosition( camera.matrixWorld ) ); + + } + + } + + if ( material.isMeshPhongMaterial || + material.isMeshLambertMaterial || + material.isMeshBasicMaterial || + material.isMeshStandardMaterial || + material.isShaderMaterial || + material.skinning ) { + + p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); + + } + + } + + // skinning uniforms must be set even if material didn't change + // auto-setting of texture unit for bone texture must go before other textures + // not sure why, but otherwise weird things happen + + if ( material.skinning ) { + + p_uniforms.setOptional( _gl, object, 'bindMatrix' ); + p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); + + var skeleton = object.skeleton; + + if ( skeleton ) { + + var bones = skeleton.bones; + + if ( capabilities.floatVertexTextures ) { + + if ( skeleton.boneTexture === undefined ) { + + // layout (1 matrix = 4 pixels) + // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4) + // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8) + // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16) + // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32) + // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64) + + + var size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix + size = _Math.ceilPowerOfTwo( size ); + size = Math.max( size, 4 ); + + var boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel + boneMatrices.set( skeleton.boneMatrices ); // copy current values + + var boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType ); + boneTexture.needsUpdate = true; + + skeleton.boneMatrices = boneMatrices; + skeleton.boneTexture = boneTexture; + skeleton.boneTextureSize = size; + + } + + p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture ); + p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); + + } else { + + p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' ); + + } + + } + + } + + if ( refreshMaterial ) { + + p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); + p_uniforms.setValue( _gl, 'toneMappingWhitePoint', _this.toneMappingWhitePoint ); + + if ( material.lights ) { + + // the current material requires lighting info + + // note: all lighting uniforms are always set correctly + // they simply reference the renderer's state for their + // values + // + // use the current material's .needsUpdate flags to set + // the GL state when required + + markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); + + } + + // refresh uniforms common to several materials + + if ( fog && material.fog ) { + + refreshUniformsFog( m_uniforms, fog ); + + } + + if ( material.isMeshBasicMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + + } else if ( material.isMeshLambertMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + refreshUniformsLambert( m_uniforms, material ); + + } else if ( material.isMeshPhongMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + + if ( material.isMeshToonMaterial ) { + + refreshUniformsToon( m_uniforms, material ); + + } else { + + refreshUniformsPhong( m_uniforms, material ); + + } + + } else if ( material.isMeshStandardMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + + if ( material.isMeshPhysicalMaterial ) { + + refreshUniformsPhysical( m_uniforms, material ); + + } else { + + refreshUniformsStandard( m_uniforms, material ); + + } + + } else if ( material.isMeshDepthMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + refreshUniformsDepth( m_uniforms, material ); + + } else if ( material.isMeshDistanceMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + refreshUniformsDistance( m_uniforms, material ); + + } else if ( material.isMeshNormalMaterial ) { + + refreshUniformsCommon( m_uniforms, material ); + refreshUniformsNormal( m_uniforms, material ); + + } else if ( material.isLineBasicMaterial ) { + + refreshUniformsLine( m_uniforms, material ); + + if ( material.isLineDashedMaterial ) { + + refreshUniformsDash( m_uniforms, material ); + + } + + } else if ( material.isPointsMaterial ) { + + refreshUniformsPoints( m_uniforms, material ); + + } else if ( material.isShadowMaterial ) { + + m_uniforms.color.value = material.color; + m_uniforms.opacity.value = material.opacity; + + } + + // RectAreaLight Texture + // TODO (mrdoob): Find a nicer implementation + + if ( m_uniforms.ltc_1 !== undefined ) m_uniforms.ltc_1.value = UniformsLib.LTC_1; + if ( m_uniforms.ltc_2 !== undefined ) m_uniforms.ltc_2.value = UniformsLib.LTC_2; + + WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this ); + + } + + if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { + + WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, _this ); + material.uniformsNeedUpdate = false; + + } + + // common matrices + + p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); + p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); + p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); + + return program; + + } + + // Uniforms (refresh uniforms objects) + + function refreshUniformsCommon( uniforms, material ) { + + uniforms.opacity.value = material.opacity; + + if ( material.color ) { + + uniforms.diffuse.value = material.color; + + } + + if ( material.emissive ) { + + uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); + + } + + if ( material.map ) { + + uniforms.map.value = material.map; + + } + + if ( material.alphaMap ) { + + uniforms.alphaMap.value = material.alphaMap; + + } + + if ( material.specularMap ) { + + uniforms.specularMap.value = material.specularMap; + + } + + if ( material.envMap ) { + + uniforms.envMap.value = material.envMap; + + // don't flip CubeTexture envMaps, flip everything else: + // WebGLRenderTargetCube will be flipped for backwards compatibility + // WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture + // this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future + uniforms.flipEnvMap.value = ( ! ( material.envMap && material.envMap.isCubeTexture ) ) ? 1 : - 1; + + uniforms.reflectivity.value = material.reflectivity; + uniforms.refractionRatio.value = material.refractionRatio; + + } + + if ( material.lightMap ) { + + uniforms.lightMap.value = material.lightMap; + uniforms.lightMapIntensity.value = material.lightMapIntensity; + + } + + if ( material.aoMap ) { + + uniforms.aoMap.value = material.aoMap; + uniforms.aoMapIntensity.value = material.aoMapIntensity; + + } + + // uv repeat and offset setting priorities + // 1. color map + // 2. specular map + // 3. normal map + // 4. bump map + // 5. alpha map + // 6. emissive map + + var uvScaleMap; + + if ( material.map ) { + + uvScaleMap = material.map; + + } else if ( material.specularMap ) { + + uvScaleMap = material.specularMap; + + } else if ( material.displacementMap ) { + + uvScaleMap = material.displacementMap; + + } else if ( material.normalMap ) { + + uvScaleMap = material.normalMap; + + } else if ( material.bumpMap ) { + + uvScaleMap = material.bumpMap; + + } else if ( material.roughnessMap ) { + + uvScaleMap = material.roughnessMap; + + } else if ( material.metalnessMap ) { + + uvScaleMap = material.metalnessMap; + + } else if ( material.alphaMap ) { + + uvScaleMap = material.alphaMap; + + } else if ( material.emissiveMap ) { + + uvScaleMap = material.emissiveMap; + + } + + if ( uvScaleMap !== undefined ) { + + // backwards compatibility + if ( uvScaleMap.isWebGLRenderTarget ) { + + uvScaleMap = uvScaleMap.texture; + + } + + if ( uvScaleMap.matrixAutoUpdate === true ) { + + var offset = uvScaleMap.offset; + var repeat = uvScaleMap.repeat; + var rotation = uvScaleMap.rotation; + var center = uvScaleMap.center; + + uvScaleMap.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); + + } + + uniforms.uvTransform.value.copy( uvScaleMap.matrix ); + + } + + } + + function refreshUniformsLine( uniforms, material ) { + + uniforms.diffuse.value = material.color; + uniforms.opacity.value = material.opacity; + + } + + function refreshUniformsDash( uniforms, material ) { + + uniforms.dashSize.value = material.dashSize; + uniforms.totalSize.value = material.dashSize + material.gapSize; + uniforms.scale.value = material.scale; + + } + + function refreshUniformsPoints( uniforms, material ) { + + uniforms.diffuse.value = material.color; + uniforms.opacity.value = material.opacity; + uniforms.size.value = material.size * _pixelRatio; + uniforms.scale.value = _height * 0.5; + + uniforms.map.value = material.map; + + if ( material.map !== null ) { + + if ( material.map.matrixAutoUpdate === true ) { + + var offset = material.map.offset; + var repeat = material.map.repeat; + var rotation = material.map.rotation; + var center = material.map.center; + + material.map.matrix.setUvTransform( offset.x, offset.y, repeat.x, repeat.y, rotation, center.x, center.y ); + + } + + uniforms.uvTransform.value.copy( material.map.matrix ); + + } + + } + + function refreshUniformsFog( uniforms, fog ) { + + uniforms.fogColor.value = fog.color; + + if ( fog.isFog ) { + + uniforms.fogNear.value = fog.near; + uniforms.fogFar.value = fog.far; + + } else if ( fog.isFogExp2 ) { + + uniforms.fogDensity.value = fog.density; + + } + + } + + function refreshUniformsLambert( uniforms, material ) { + + if ( material.emissiveMap ) { + + uniforms.emissiveMap.value = material.emissiveMap; + + } + + } + + function refreshUniformsPhong( uniforms, material ) { + + uniforms.specular.value = material.specular; + uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) + + if ( material.emissiveMap ) { + + uniforms.emissiveMap.value = material.emissiveMap; + + } + + if ( material.bumpMap ) { + + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; + + } + + if ( material.normalMap ) { + + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); + + } + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + } + + function refreshUniformsToon( uniforms, material ) { + + refreshUniformsPhong( uniforms, material ); + + if ( material.gradientMap ) { + + uniforms.gradientMap.value = material.gradientMap; + + } + + } + + function refreshUniformsStandard( uniforms, material ) { + + uniforms.roughness.value = material.roughness; + uniforms.metalness.value = material.metalness; + + if ( material.roughnessMap ) { + + uniforms.roughnessMap.value = material.roughnessMap; + + } + + if ( material.metalnessMap ) { + + uniforms.metalnessMap.value = material.metalnessMap; + + } + + if ( material.emissiveMap ) { + + uniforms.emissiveMap.value = material.emissiveMap; + + } + + if ( material.bumpMap ) { + + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; + + } + + if ( material.normalMap ) { + + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); + + } + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + if ( material.envMap ) { + + //uniforms.envMap.value = material.envMap; // part of uniforms common + uniforms.envMapIntensity.value = material.envMapIntensity; + + } + + } + + function refreshUniformsPhysical( uniforms, material ) { + + uniforms.clearCoat.value = material.clearCoat; + uniforms.clearCoatRoughness.value = material.clearCoatRoughness; + + refreshUniformsStandard( uniforms, material ); + + } + + function refreshUniformsDepth( uniforms, material ) { + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + } + + function refreshUniformsDistance( uniforms, material ) { + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + uniforms.referencePosition.value.copy( material.referencePosition ); + uniforms.nearDistance.value = material.nearDistance; + uniforms.farDistance.value = material.farDistance; + + } + + function refreshUniformsNormal( uniforms, material ) { + + if ( material.bumpMap ) { + + uniforms.bumpMap.value = material.bumpMap; + uniforms.bumpScale.value = material.bumpScale; + + } + + if ( material.normalMap ) { + + uniforms.normalMap.value = material.normalMap; + uniforms.normalScale.value.copy( material.normalScale ); + + } + + if ( material.displacementMap ) { + + uniforms.displacementMap.value = material.displacementMap; + uniforms.displacementScale.value = material.displacementScale; + uniforms.displacementBias.value = material.displacementBias; + + } + + } + + // If uniforms are marked as clean, they don't need to be loaded to the GPU. + + function markUniformsLightsNeedsUpdate( uniforms, value ) { + + uniforms.ambientLightColor.needsUpdate = value; + + uniforms.directionalLights.needsUpdate = value; + uniforms.pointLights.needsUpdate = value; + uniforms.spotLights.needsUpdate = value; + uniforms.rectAreaLights.needsUpdate = value; + uniforms.hemisphereLights.needsUpdate = value; + + } + + // Textures + + function allocTextureUnit() { + + var textureUnit = _usedTextureUnits; + + if ( textureUnit >= capabilities.maxTextures ) { + + console.warn( 'THREE.WebGLRenderer: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); + + } + + _usedTextureUnits += 1; + + return textureUnit; + + } + + this.allocTextureUnit = allocTextureUnit; + + // this.setTexture2D = setTexture2D; + this.setTexture2D = ( function () { + + var warned = false; + + // backwards compatibility: peel texture.texture + return function setTexture2D( texture, slot ) { + + if ( texture && texture.isWebGLRenderTarget ) { + + if ( ! warned ) { + + console.warn( "THREE.WebGLRenderer.setTexture2D: don't use render targets as textures. Use their .texture property instead." ); + warned = true; + + } + + texture = texture.texture; + + } + + textures.setTexture2D( texture, slot ); + + }; + + }() ); + + this.setTexture = ( function () { + + var warned = false; + + return function setTexture( texture, slot ) { + + if ( ! warned ) { + + console.warn( "THREE.WebGLRenderer: .setTexture is deprecated, use setTexture2D instead." ); + warned = true; + + } + + textures.setTexture2D( texture, slot ); + + }; + + }() ); + + this.setTextureCube = ( function () { + + var warned = false; + + return function setTextureCube( texture, slot ) { + + // backwards compatibility: peel texture.texture + if ( texture && texture.isWebGLRenderTargetCube ) { + + if ( ! warned ) { + + console.warn( "THREE.WebGLRenderer.setTextureCube: don't use cube render targets as textures. Use their .texture property instead." ); + warned = true; + + } + + texture = texture.texture; + + } + + // currently relying on the fact that WebGLRenderTargetCube.texture is a Texture and NOT a CubeTexture + // TODO: unify these code paths + if ( ( texture && texture.isCubeTexture ) || + ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) { + + // CompressedTexture can have Array in image :/ + + // this function alone should take care of cube textures + textures.setTextureCube( texture, slot ); + + } else { + + // assumed: texture property of THREE.WebGLRenderTargetCube + + textures.setTextureCubeDynamic( texture, slot ); + + } + + }; + + }() ); + + this.getRenderTarget = function () { + + return _currentRenderTarget; + + }; + + this.setRenderTarget = function ( renderTarget ) { + + _currentRenderTarget = renderTarget; + + if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) { + + textures.setupRenderTarget( renderTarget ); + + } + + var framebuffer = null; + var isCube = false; + + if ( renderTarget ) { + + var __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; + + if ( renderTarget.isWebGLRenderTargetCube ) { + + framebuffer = __webglFramebuffer[ renderTarget.activeCubeFace ]; + isCube = true; + + } else { + + framebuffer = __webglFramebuffer; + + } + + _currentViewport.copy( renderTarget.viewport ); + _currentScissor.copy( renderTarget.scissor ); + _currentScissorTest = renderTarget.scissorTest; + + } else { + + _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ); + _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ); + _currentScissorTest = _scissorTest; + + } + + if ( _currentFramebuffer !== framebuffer ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + _currentFramebuffer = framebuffer; + + } + + state.viewport( _currentViewport ); + state.scissor( _currentScissor ); + state.setScissorTest( _currentScissorTest ); + + if ( isCube ) { + + var textureProperties = properties.get( renderTarget.texture ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, textureProperties.__webglTexture, renderTarget.activeMipMapLevel ); + + } + + }; + + this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer ) { + + if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { + + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); + return; + + } + + var framebuffer = properties.get( renderTarget ).__webglFramebuffer; + + if ( framebuffer ) { + + var restore = false; + + if ( framebuffer !== _currentFramebuffer ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); + + restore = true; + + } + + try { + + var texture = renderTarget.texture; + var textureFormat = texture.format; + var textureType = texture.type; + + if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { + + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); + return; + + } + + if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // IE11, Edge and Chrome Mac < 52 (#9513) + ! ( textureType === FloatType && ( extensions.get( 'OES_texture_float' ) || extensions.get( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox + ! ( textureType === HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) { + + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); + return; + + } + + if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) { + + // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) + + if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { + + _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); + + } + + } else { + + console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' ); + + } + + } finally { + + if ( restore ) { + + _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer ); + + } + + } + + } + + }; + + this.copyFramebufferToTexture = function ( position, texture, level ) { + + var width = texture.image.width; + var height = texture.image.height; + var internalFormat = utils.convert( texture.format ); + + this.setTexture2D( texture, 0 ); + + _gl.copyTexImage2D( _gl.TEXTURE_2D, level || 0, internalFormat, position.x, position.y, width, height, 0 ); + + }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function FogExp2( color, density ) { + + this.name = ''; + + this.color = new Color( color ); + this.density = ( density !== undefined ) ? density : 0.00025; + + } + + FogExp2.prototype.isFogExp2 = true; + + FogExp2.prototype.clone = function () { + + return new FogExp2( this.color.getHex(), this.density ); + + }; + + FogExp2.prototype.toJSON = function ( /* meta */ ) { + + return { + type: 'FogExp2', + color: this.color.getHex(), + density: this.density + }; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function Fog( color, near, far ) { + + this.name = ''; + + this.color = new Color( color ); + + this.near = ( near !== undefined ) ? near : 1; + this.far = ( far !== undefined ) ? far : 1000; + + } + + Fog.prototype.isFog = true; + + Fog.prototype.clone = function () { + + return new Fog( this.color.getHex(), this.near, this.far ); + + }; + + Fog.prototype.toJSON = function ( /* meta */ ) { + + return { + type: 'Fog', + color: this.color.getHex(), + near: this.near, + far: this.far + }; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Scene() { + + Object3D.call( this ); + + this.type = 'Scene'; + + this.background = null; + this.fog = null; + this.overrideMaterial = null; + + this.autoUpdate = true; // checked by the renderer + + } + + Scene.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Scene, + + copy: function ( source, recursive ) { + + Object3D.prototype.copy.call( this, source, recursive ); + + if ( source.background !== null ) this.background = source.background.clone(); + if ( source.fog !== null ) this.fog = source.fog.clone(); + if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone(); + + this.autoUpdate = source.autoUpdate; + this.matrixAutoUpdate = source.matrixAutoUpdate; + + return this; + + }, + + toJSON: function ( meta ) { + + var data = Object3D.prototype.toJSON.call( this, meta ); + + if ( this.background !== null ) data.object.background = this.background.toJSON( meta ); + if ( this.fog !== null ) data.object.fog = this.fog.toJSON(); + + return data; + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * uvOffset: new THREE.Vector2(), + * uvScale: new THREE.Vector2() + * } + */ + + function SpriteMaterial( parameters ) { + + Material.call( this ); + + this.type = 'SpriteMaterial'; + + this.color = new Color( 0xffffff ); + this.map = null; + + this.rotation = 0; + + this.fog = false; + this.lights = false; + + this.setValues( parameters ); + + } + + SpriteMaterial.prototype = Object.create( Material.prototype ); + SpriteMaterial.prototype.constructor = SpriteMaterial; + SpriteMaterial.prototype.isSpriteMaterial = true; + + SpriteMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + this.map = source.map; + + this.rotation = source.rotation; + + return this; + + }; + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + */ + + function Sprite( material ) { + + Object3D.call( this ); + + this.type = 'Sprite'; + + this.material = ( material !== undefined ) ? material : new SpriteMaterial(); + + this.center = new Vector2( 0.5, 0.5 ); + + } + + Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Sprite, + + isSprite: true, + + raycast: ( function () { + + var intersectPoint = new Vector3(); + var worldPosition = new Vector3(); + var worldScale = new Vector3(); + + return function raycast( raycaster, intersects ) { + + worldPosition.setFromMatrixPosition( this.matrixWorld ); + raycaster.ray.closestPointToPoint( worldPosition, intersectPoint ); + + worldScale.setFromMatrixScale( this.matrixWorld ); + var guessSizeSq = worldScale.x * worldScale.y / 4; + + if ( worldPosition.distanceToSquared( intersectPoint ) > guessSizeSq ) return; + + var distance = raycaster.ray.origin.distanceTo( intersectPoint ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + intersects.push( { + + distance: distance, + point: intersectPoint.clone(), + face: null, + object: this + + } ); + + }; + + }() ), + + clone: function () { + + return new this.constructor( this.material ).copy( this ); + + }, + + copy: function ( source ) { + + Object3D.prototype.copy.call( this, source ); + + if ( source.center !== undefined ) this.center.copy( source.center ); + + return this; + + } + + + } ); + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + + function LOD() { + + Object3D.call( this ); + + this.type = 'LOD'; + + Object.defineProperties( this, { + levels: { + enumerable: true, + value: [] + } + } ); + + } + + LOD.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: LOD, + + copy: function ( source ) { + + Object3D.prototype.copy.call( this, source, false ); + + var levels = source.levels; + + for ( var i = 0, l = levels.length; i < l; i ++ ) { + + var level = levels[ i ]; + + this.addLevel( level.object.clone(), level.distance ); + + } + + return this; + + }, + + addLevel: function ( object, distance ) { + + if ( distance === undefined ) distance = 0; + + distance = Math.abs( distance ); + + var levels = this.levels; + + for ( var l = 0; l < levels.length; l ++ ) { + + if ( distance < levels[ l ].distance ) { + + break; + + } + + } + + levels.splice( l, 0, { distance: distance, object: object } ); + + this.add( object ); + + }, + + getObjectForDistance: function ( distance ) { + + var levels = this.levels; + + for ( var i = 1, l = levels.length; i < l; i ++ ) { + + if ( distance < levels[ i ].distance ) { + + break; + + } + + } + + return levels[ i - 1 ].object; + + }, + + raycast: ( function () { + + var matrixPosition = new Vector3(); + + return function raycast( raycaster, intersects ) { + + matrixPosition.setFromMatrixPosition( this.matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( matrixPosition ); + + this.getObjectForDistance( distance ).raycast( raycaster, intersects ); + + }; + + }() ), + + update: function () { + + var v1 = new Vector3(); + var v2 = new Vector3(); + + return function update( camera ) { + + var levels = this.levels; + + if ( levels.length > 1 ) { + + v1.setFromMatrixPosition( camera.matrixWorld ); + v2.setFromMatrixPosition( this.matrixWorld ); + + var distance = v1.distanceTo( v2 ); + + levels[ 0 ].object.visible = true; + + for ( var i = 1, l = levels.length; i < l; i ++ ) { + + if ( distance >= levels[ i ].distance ) { + + levels[ i - 1 ].object.visible = false; + levels[ i ].object.visible = true; + + } else { + + break; + + } + + } + + for ( ; i < l; i ++ ) { + + levels[ i ].object.visible = false; + + } + + } + + }; + + }(), + + toJSON: function ( meta ) { + + var data = Object3D.prototype.toJSON.call( this, meta ); + + data.object.levels = []; + + var levels = this.levels; + + for ( var i = 0, l = levels.length; i < l; i ++ ) { + + var level = levels[ i ]; + + data.object.levels.push( { + object: level.object.uuid, + distance: level.distance + } ); + + } + + return data; + + } + + } ); + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author michael guerrero / http://realitymeltdown.com + * @author ikerr / http://verold.com + */ + + function Skeleton( bones, boneInverses ) { + + // copy the bone array + + bones = bones || []; + + this.bones = bones.slice( 0 ); + this.boneMatrices = new Float32Array( this.bones.length * 16 ); + + // use the supplied bone inverses or calculate the inverses + + if ( boneInverses === undefined ) { + + this.calculateInverses(); + + } else { + + if ( this.bones.length === boneInverses.length ) { + + this.boneInverses = boneInverses.slice( 0 ); + + } else { + + console.warn( 'THREE.Skeleton boneInverses is the wrong length.' ); + + this.boneInverses = []; + + for ( var i = 0, il = this.bones.length; i < il; i ++ ) { + + this.boneInverses.push( new Matrix4() ); + + } + + } + + } + + } + + Object.assign( Skeleton.prototype, { + + calculateInverses: function () { + + this.boneInverses = []; + + for ( var i = 0, il = this.bones.length; i < il; i ++ ) { + + var inverse = new Matrix4(); + + if ( this.bones[ i ] ) { + + inverse.getInverse( this.bones[ i ].matrixWorld ); + + } + + this.boneInverses.push( inverse ); + + } + + }, + + pose: function () { + + var bone, i, il; + + // recover the bind-time world matrices + + for ( i = 0, il = this.bones.length; i < il; i ++ ) { + + bone = this.bones[ i ]; + + if ( bone ) { + + bone.matrixWorld.getInverse( this.boneInverses[ i ] ); + + } + + } + + // compute the local matrices, positions, rotations and scales + + for ( i = 0, il = this.bones.length; i < il; i ++ ) { + + bone = this.bones[ i ]; + + if ( bone ) { + + if ( bone.parent && bone.parent.isBone ) { + + bone.matrix.getInverse( bone.parent.matrixWorld ); + bone.matrix.multiply( bone.matrixWorld ); + + } else { + + bone.matrix.copy( bone.matrixWorld ); + + } + + bone.matrix.decompose( bone.position, bone.quaternion, bone.scale ); + + } + + } + + }, + + update: ( function () { + + var offsetMatrix = new Matrix4(); + var identityMatrix = new Matrix4(); + + return function update() { + + var bones = this.bones; + var boneInverses = this.boneInverses; + var boneMatrices = this.boneMatrices; + var boneTexture = this.boneTexture; + + // flatten bone matrices to array + + for ( var i = 0, il = bones.length; i < il; i ++ ) { + + // compute the offset between the current and the original transform + + var matrix = bones[ i ] ? bones[ i ].matrixWorld : identityMatrix; + + offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] ); + offsetMatrix.toArray( boneMatrices, i * 16 ); + + } + + if ( boneTexture !== undefined ) { + + boneTexture.needsUpdate = true; + + } + + }; + + } )(), + + clone: function () { + + return new Skeleton( this.bones, this.boneInverses ); + + }, + + getBoneByName: function ( name ) { + + for ( var i = 0, il = this.bones.length; i < il; i ++ ) { + + var bone = this.bones[ i ]; + + if ( bone.name === name ) { + + return bone; + + } + + } + + return undefined; + + } + + } ); + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com + */ + + function Bone() { + + Object3D.call( this ); + + this.type = 'Bone'; + + } + + Bone.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Bone, + + isBone: true + + } ); + + /** + * @author mikael emtinger / http://gomo.se/ + * @author alteredq / http://alteredqualia.com/ + * @author ikerr / http://verold.com + */ + + function SkinnedMesh( geometry, material ) { + + Mesh.call( this, geometry, material ); + + this.type = 'SkinnedMesh'; + + this.bindMode = 'attached'; + this.bindMatrix = new Matrix4(); + this.bindMatrixInverse = new Matrix4(); + + var bones = this.initBones(); + var skeleton = new Skeleton( bones ); + + this.bind( skeleton, this.matrixWorld ); + + this.normalizeSkinWeights(); + + } + + SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), { + + constructor: SkinnedMesh, + + isSkinnedMesh: true, + + initBones: function () { + + var bones = [], bone, gbone; + var i, il; + + if ( this.geometry && this.geometry.bones !== undefined ) { + + // first, create array of 'Bone' objects from geometry data + + for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) { + + gbone = this.geometry.bones[ i ]; + + // create new 'Bone' object + + bone = new Bone(); + bones.push( bone ); + + // apply values + + bone.name = gbone.name; + bone.position.fromArray( gbone.pos ); + bone.quaternion.fromArray( gbone.rotq ); + if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); + + } + + // second, create bone hierarchy + + for ( i = 0, il = this.geometry.bones.length; i < il; i ++ ) { + + gbone = this.geometry.bones[ i ]; + + if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) { + + // subsequent bones in the hierarchy + + bones[ gbone.parent ].add( bones[ i ] ); + + } else { + + // topmost bone, immediate child of the skinned mesh + + this.add( bones[ i ] ); + + } + + } + + } + + // now the bones are part of the scene graph and children of the skinned mesh. + // let's update the corresponding matrices + + this.updateMatrixWorld( true ); + + return bones; + + }, + + bind: function ( skeleton, bindMatrix ) { + + this.skeleton = skeleton; + + if ( bindMatrix === undefined ) { + + this.updateMatrixWorld( true ); + + this.skeleton.calculateInverses(); + + bindMatrix = this.matrixWorld; + + } + + this.bindMatrix.copy( bindMatrix ); + this.bindMatrixInverse.getInverse( bindMatrix ); + + }, + + pose: function () { + + this.skeleton.pose(); + + }, + + normalizeSkinWeights: function () { + + var scale, i; + + if ( this.geometry && this.geometry.isGeometry ) { + + for ( i = 0; i < this.geometry.skinWeights.length; i ++ ) { + + var sw = this.geometry.skinWeights[ i ]; + + scale = 1.0 / sw.manhattanLength(); + + if ( scale !== Infinity ) { + + sw.multiplyScalar( scale ); + + } else { + + sw.set( 1, 0, 0, 0 ); // do something reasonable + + } + + } + + } else if ( this.geometry && this.geometry.isBufferGeometry ) { + + var vec = new Vector4(); + + var skinWeight = this.geometry.attributes.skinWeight; + + for ( i = 0; i < skinWeight.count; i ++ ) { + + vec.x = skinWeight.getX( i ); + vec.y = skinWeight.getY( i ); + vec.z = skinWeight.getZ( i ); + vec.w = skinWeight.getW( i ); + + scale = 1.0 / vec.manhattanLength(); + + if ( scale !== Infinity ) { + + vec.multiplyScalar( scale ); + + } else { + + vec.set( 1, 0, 0, 0 ); // do something reasonable + + } + + skinWeight.setXYZW( i, vec.x, vec.y, vec.z, vec.w ); + + } + + } + + }, + + updateMatrixWorld: function ( force ) { + + Mesh.prototype.updateMatrixWorld.call( this, force ); + + if ( this.bindMode === 'attached' ) { + + this.bindMatrixInverse.getInverse( this.matrixWorld ); + + } else if ( this.bindMode === 'detached' ) { + + this.bindMatrixInverse.getInverse( this.bindMatrix ); + + } else { + + console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode ); + + } + + }, + + clone: function () { + + return new this.constructor( this.geometry, this.material ).copy( this ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * linewidth: , + * linecap: "round", + * linejoin: "round" + * } + */ + + function LineBasicMaterial( parameters ) { + + Material.call( this ); + + this.type = 'LineBasicMaterial'; + + this.color = new Color( 0xffffff ); + + this.linewidth = 1; + this.linecap = 'round'; + this.linejoin = 'round'; + + this.lights = false; + + this.setValues( parameters ); + + } + + LineBasicMaterial.prototype = Object.create( Material.prototype ); + LineBasicMaterial.prototype.constructor = LineBasicMaterial; + + LineBasicMaterial.prototype.isLineBasicMaterial = true; + + LineBasicMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + this.linewidth = source.linewidth; + this.linecap = source.linecap; + this.linejoin = source.linejoin; + + return this; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Line( geometry, material, mode ) { + + if ( mode === 1 ) { + + console.warn( 'THREE.Line: parameter THREE.LinePieces no longer supported. Created THREE.LineSegments instead.' ); + return new LineSegments( geometry, material ); + + } + + Object3D.call( this ); + + this.type = 'Line'; + + this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); + this.material = material !== undefined ? material : new LineBasicMaterial( { color: Math.random() * 0xffffff } ); + + } + + Line.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Line, + + isLine: true, + + computeLineDistances: ( function () { + + var start = new Vector3(); + var end = new Vector3(); + + return function computeLineDistances() { + + var geometry = this.geometry; + + if ( geometry.isBufferGeometry ) { + + // we assume non-indexed geometry + + if ( geometry.index === null ) { + + var positionAttribute = geometry.attributes.position; + var lineDistances = [ 0 ]; + + for ( var i = 1, l = positionAttribute.count; i < l; i ++ ) { + + start.fromBufferAttribute( positionAttribute, i - 1 ); + end.fromBufferAttribute( positionAttribute, i ); + + lineDistances[ i ] = lineDistances[ i - 1 ]; + lineDistances[ i ] += start.distanceTo( end ); + + } + + geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) ); + + } else { + + console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + + } + + } else if ( geometry.isGeometry ) { + + var vertices = geometry.vertices; + var lineDistances = geometry.lineDistances; + + lineDistances[ 0 ] = 0; + + for ( var i = 1, l = vertices.length; i < l; i ++ ) { + + lineDistances[ i ] = lineDistances[ i - 1 ]; + lineDistances[ i ] += vertices[ i - 1 ].distanceTo( vertices[ i ] ); + + } + + } + + return this; + + }; + + }() ), + + raycast: ( function () { + + var inverseMatrix = new Matrix4(); + var ray = new Ray(); + var sphere = new Sphere(); + + return function raycast( raycaster, intersects ) { + + var precision = raycaster.linePrecision; + var precisionSq = precision * precision; + + var geometry = this.geometry; + var matrixWorld = this.matrixWorld; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( matrixWorld ); + + if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; + + // + + inverseMatrix.getInverse( matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + var vStart = new Vector3(); + var vEnd = new Vector3(); + var interSegment = new Vector3(); + var interRay = new Vector3(); + var step = ( this && this.isLineSegments ) ? 2 : 1; + + if ( geometry.isBufferGeometry ) { + + var index = geometry.index; + var attributes = geometry.attributes; + var positions = attributes.position.array; + + if ( index !== null ) { + + var indices = index.array; + + for ( var i = 0, l = indices.length - 1; i < l; i += step ) { + + var a = indices[ i ]; + var b = indices[ i + 1 ]; + + vStart.fromArray( positions, a * 3 ); + vEnd.fromArray( positions, b * 3 ); + + var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + + var distance = raycaster.ray.origin.distanceTo( interRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this + + } ); + + } + + } else { + + for ( var i = 0, l = positions.length / 3 - 1; i < l; i += step ) { + + vStart.fromArray( positions, 3 * i ); + vEnd.fromArray( positions, 3 * i + 3 ); + + var distSq = ray.distanceSqToSegment( vStart, vEnd, interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + + var distance = raycaster.ray.origin.distanceTo( interRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this + + } ); + + } + + } + + } else if ( geometry.isGeometry ) { + + var vertices = geometry.vertices; + var nbVertices = vertices.length; + + for ( var i = 0; i < nbVertices - 1; i += step ) { + + var distSq = ray.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment ); + + if ( distSq > precisionSq ) continue; + + interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation + + var distance = raycaster.ray.origin.distanceTo( interRay ); + + if ( distance < raycaster.near || distance > raycaster.far ) continue; + + intersects.push( { + + distance: distance, + // What do we want? intersection point on the ray or on the segment?? + // point: raycaster.ray.at( distance ), + point: interSegment.clone().applyMatrix4( this.matrixWorld ), + index: i, + face: null, + faceIndex: null, + object: this + + } ); + + } + + } + + }; + + }() ), + + clone: function () { + + return new this.constructor( this.geometry, this.material ).copy( this ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function LineSegments( geometry, material ) { + + Line.call( this, geometry, material ); + + this.type = 'LineSegments'; + + } + + LineSegments.prototype = Object.assign( Object.create( Line.prototype ), { + + constructor: LineSegments, + + isLineSegments: true, + + computeLineDistances: ( function () { + + var start = new Vector3(); + var end = new Vector3(); + + return function computeLineDistances() { + + var geometry = this.geometry; + + if ( geometry.isBufferGeometry ) { + + // we assume non-indexed geometry + + if ( geometry.index === null ) { + + var positionAttribute = geometry.attributes.position; + var lineDistances = []; + + for ( var i = 0, l = positionAttribute.count; i < l; i += 2 ) { + + start.fromBufferAttribute( positionAttribute, i ); + end.fromBufferAttribute( positionAttribute, i + 1 ); + + lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; + lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end ); + + } + + geometry.addAttribute( 'lineDistance', new THREE.Float32BufferAttribute( lineDistances, 1 ) ); + + } else { + + console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' ); + + } + + } else if ( geometry.isGeometry ) { + + var vertices = geometry.vertices; + var lineDistances = geometry.lineDistances; + + for ( var i = 0, l = vertices.length; i < l; i += 2 ) { + + start.copy( vertices[ i ] ); + end.copy( vertices[ i + 1 ] ); + + lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ]; + lineDistances[ i + 1 ] = lineDistances[ i ] + start.distanceTo( end ); + + } + + } + + return this; + + }; + + }() ) + + } ); + + /** + * @author mgreter / http://github.com/mgreter + */ + + function LineLoop( geometry, material ) { + + Line.call( this, geometry, material ); + + this.type = 'LineLoop'; + + } + + LineLoop.prototype = Object.assign( Object.create( Line.prototype ), { + + constructor: LineLoop, + + isLineLoop: true, + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * map: new THREE.Texture( ), + * + * size: , + * sizeAttenuation: + * } + */ + + function PointsMaterial( parameters ) { + + Material.call( this ); + + this.type = 'PointsMaterial'; + + this.color = new Color( 0xffffff ); + + this.map = null; + + this.size = 1; + this.sizeAttenuation = true; + + this.lights = false; + + this.setValues( parameters ); + + } + + PointsMaterial.prototype = Object.create( Material.prototype ); + PointsMaterial.prototype.constructor = PointsMaterial; + + PointsMaterial.prototype.isPointsMaterial = true; + + PointsMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.size = source.size; + this.sizeAttenuation = source.sizeAttenuation; + + return this; + + }; + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function Points( geometry, material ) { + + Object3D.call( this ); + + this.type = 'Points'; + + this.geometry = geometry !== undefined ? geometry : new BufferGeometry(); + this.material = material !== undefined ? material : new PointsMaterial( { color: Math.random() * 0xffffff } ); + + } + + Points.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Points, + + isPoints: true, + + raycast: ( function () { + + var inverseMatrix = new Matrix4(); + var ray = new Ray(); + var sphere = new Sphere(); + + return function raycast( raycaster, intersects ) { + + var object = this; + var geometry = this.geometry; + var matrixWorld = this.matrixWorld; + var threshold = raycaster.params.Points.threshold; + + // Checking boundingSphere distance to ray + + if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); + + sphere.copy( geometry.boundingSphere ); + sphere.applyMatrix4( matrixWorld ); + sphere.radius += threshold; + + if ( raycaster.ray.intersectsSphere( sphere ) === false ) return; + + // + + inverseMatrix.getInverse( matrixWorld ); + ray.copy( raycaster.ray ).applyMatrix4( inverseMatrix ); + + var localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 ); + var localThresholdSq = localThreshold * localThreshold; + var position = new Vector3(); + + function testPoint( point, index ) { + + var rayPointDistanceSq = ray.distanceSqToPoint( point ); + + if ( rayPointDistanceSq < localThresholdSq ) { + + var intersectPoint = ray.closestPointToPoint( point ); + intersectPoint.applyMatrix4( matrixWorld ); + + var distance = raycaster.ray.origin.distanceTo( intersectPoint ); + + if ( distance < raycaster.near || distance > raycaster.far ) return; + + intersects.push( { + + distance: distance, + distanceToRay: Math.sqrt( rayPointDistanceSq ), + point: intersectPoint.clone(), + index: index, + face: null, + object: object + + } ); + + } + + } + + if ( geometry.isBufferGeometry ) { + + var index = geometry.index; + var attributes = geometry.attributes; + var positions = attributes.position.array; + + if ( index !== null ) { + + var indices = index.array; + + for ( var i = 0, il = indices.length; i < il; i ++ ) { + + var a = indices[ i ]; + + position.fromArray( positions, a * 3 ); + + testPoint( position, a ); + + } + + } else { + + for ( var i = 0, l = positions.length / 3; i < l; i ++ ) { + + position.fromArray( positions, i * 3 ); + + testPoint( position, i ); + + } + + } + + } else { + + var vertices = geometry.vertices; + + for ( var i = 0, l = vertices.length; i < l; i ++ ) { + + testPoint( vertices[ i ], i ); + + } + + } + + }; + + }() ), + + clone: function () { + + return new this.constructor( this.geometry, this.material ).copy( this ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Group() { + + Object3D.call( this ); + + this.type = 'Group'; + + } + + Group.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Group, + + isGroup: true + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) { + + Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.generateMipmaps = false; + + } + + VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), { + + constructor: VideoTexture, + + isVideoTexture: true, + + update: function () { + + var video = this.image; + + if ( video.readyState >= video.HAVE_CURRENT_DATA ) { + + this.needsUpdate = true; + + } + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) { + + Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ); + + this.image = { width: width, height: height }; + this.mipmaps = mipmaps; + + // no flipping for cube textures + // (also flipping doesn't work for compressed textures ) + + this.flipY = false; + + // can't generate mipmaps for compressed textures + // mips must be embedded in DDS files + + this.generateMipmaps = false; + + } + + CompressedTexture.prototype = Object.create( Texture.prototype ); + CompressedTexture.prototype.constructor = CompressedTexture; + + CompressedTexture.prototype.isCompressedTexture = true; + + /** + * @author Matt DesLauriers / @mattdesl + * @author atix / arthursilber.de + */ + + function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) { + + format = format !== undefined ? format : DepthFormat; + + if ( format !== DepthFormat && format !== DepthStencilFormat ) { + + throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' ); + + } + + if ( type === undefined && format === DepthFormat ) type = UnsignedShortType; + if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type; + + Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ); + + this.image = { width: width, height: height }; + + this.magFilter = magFilter !== undefined ? magFilter : NearestFilter; + this.minFilter = minFilter !== undefined ? minFilter : NearestFilter; + + this.flipY = false; + this.generateMipmaps = false; + + } + + DepthTexture.prototype = Object.create( Texture.prototype ); + DepthTexture.prototype.constructor = DepthTexture; + DepthTexture.prototype.isDepthTexture = true; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + function WireframeGeometry( geometry ) { + + BufferGeometry.call( this ); + + this.type = 'WireframeGeometry'; + + // buffer + + var vertices = []; + + // helper variables + + var i, j, l, o, ol; + var edge = [ 0, 0 ], edges = {}, e, edge1, edge2; + var key, keys = [ 'a', 'b', 'c' ]; + var vertex; + + // different logic for Geometry and BufferGeometry + + if ( geometry && geometry.isGeometry ) { + + // create a data structure that contains all edges without duplicates + + var faces = geometry.faces; + + for ( i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( j = 0; j < 3; j ++ ) { + + edge1 = face[ keys[ j ] ]; + edge2 = face[ keys[ ( j + 1 ) % 3 ] ]; + edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates + edge[ 1 ] = Math.max( edge1, edge2 ); + + key = edge[ 0 ] + ',' + edge[ 1 ]; + + if ( edges[ key ] === undefined ) { + + edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] }; + + } + + } + + } + + // generate vertices + + for ( key in edges ) { + + e = edges[ key ]; + + vertex = geometry.vertices[ e.index1 ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + vertex = geometry.vertices[ e.index2 ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } else if ( geometry && geometry.isBufferGeometry ) { + + var position, indices, groups; + var group, start, count; + var index1, index2; + + vertex = new Vector3(); + + if ( geometry.index !== null ) { + + // indexed BufferGeometry + + position = geometry.attributes.position; + indices = geometry.index; + groups = geometry.groups; + + if ( groups.length === 0 ) { + + groups = [ { start: 0, count: indices.count, materialIndex: 0 } ]; + + } + + // create a data structure that contains all eges without duplicates + + for ( o = 0, ol = groups.length; o < ol; ++ o ) { + + group = groups[ o ]; + + start = group.start; + count = group.count; + + for ( i = start, l = ( start + count ); i < l; i += 3 ) { + + for ( j = 0; j < 3; j ++ ) { + + edge1 = indices.getX( i + j ); + edge2 = indices.getX( i + ( j + 1 ) % 3 ); + edge[ 0 ] = Math.min( edge1, edge2 ); // sorting prevents duplicates + edge[ 1 ] = Math.max( edge1, edge2 ); + + key = edge[ 0 ] + ',' + edge[ 1 ]; + + if ( edges[ key ] === undefined ) { + + edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ] }; + + } + + } + + } + + } + + // generate vertices + + for ( key in edges ) { + + e = edges[ key ]; + + vertex.fromBufferAttribute( position, e.index1 ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + vertex.fromBufferAttribute( position, e.index2 ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } else { + + // non-indexed BufferGeometry + + position = geometry.attributes.position; + + for ( i = 0, l = ( position.count / 3 ); i < l; i ++ ) { + + for ( j = 0; j < 3; j ++ ) { + + // three edges per triangle, an edge is represented as (index1, index2) + // e.g. the first triangle has the following edges: (0,1),(1,2),(2,0) + + index1 = 3 * i + j; + vertex.fromBufferAttribute( position, index1 ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + index2 = 3 * i + ( ( j + 1 ) % 3 ); + vertex.fromBufferAttribute( position, index2 ); + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } + + } + + } + + // build geometry + + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + + WireframeGeometry.prototype = Object.create( BufferGeometry.prototype ); + WireframeGeometry.prototype.constructor = WireframeGeometry; + + /** + * @author zz85 / https://github.com/zz85 + * @author Mugen87 / https://github.com/Mugen87 + * + * Parametric Surfaces Geometry + * based on the brilliant article by @prideout http://prideout.net/blog/?p=44 + */ + + // ParametricGeometry + + function ParametricGeometry( func, slices, stacks ) { + + Geometry.call( this ); + + this.type = 'ParametricGeometry'; + + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; + + this.fromBufferGeometry( new ParametricBufferGeometry( func, slices, stacks ) ); + this.mergeVertices(); + + } + + ParametricGeometry.prototype = Object.create( Geometry.prototype ); + ParametricGeometry.prototype.constructor = ParametricGeometry; + + // ParametricBufferGeometry + + function ParametricBufferGeometry( func, slices, stacks ) { + + BufferGeometry.call( this ); + + this.type = 'ParametricBufferGeometry'; + + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + var EPS = 0.00001; + + var normal = new Vector3(); + + var p0 = new Vector3(), p1 = new Vector3(); + var pu = new Vector3(), pv = new Vector3(); + + var i, j; + + // generate vertices, normals and uvs + + var sliceCount = slices + 1; + + for ( i = 0; i <= stacks; i ++ ) { + + var v = i / stacks; + + for ( j = 0; j <= slices; j ++ ) { + + var u = j / slices; + + // vertex + + p0 = func( u, v, p0 ); + vertices.push( p0.x, p0.y, p0.z ); + + // normal + + // approximate tangent vectors via finite differences + + if ( u - EPS >= 0 ) { + + p1 = func( u - EPS, v, p1 ); + pu.subVectors( p0, p1 ); + + } else { + + p1 = func( u + EPS, v, p1 ); + pu.subVectors( p1, p0 ); + + } + + if ( v - EPS >= 0 ) { + + p1 = func( u, v - EPS, p1 ); + pv.subVectors( p0, p1 ); + + } else { + + p1 = func( u, v + EPS, p1 ); + pv.subVectors( p1, p0 ); + + } + + // cross product of tangent vectors returns surface normal + + normal.crossVectors( pu, pv ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, v ); + + } + + } + + // generate indices + + for ( i = 0; i < stacks; i ++ ) { + + for ( j = 0; j < slices; j ++ ) { + + var a = i * sliceCount + j; + var b = i * sliceCount + j + 1; + var c = ( i + 1 ) * sliceCount + j + 1; + var d = ( i + 1 ) * sliceCount + j; + + // faces one and two + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + ParametricBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + ParametricBufferGeometry.prototype.constructor = ParametricBufferGeometry; + + /** + * @author clockworkgeek / https://github.com/clockworkgeek + * @author timothypratley / https://github.com/timothypratley + * @author WestLangley / http://github.com/WestLangley + * @author Mugen87 / https://github.com/Mugen87 + */ + + // PolyhedronGeometry + + function PolyhedronGeometry( vertices, indices, radius, detail ) { + + Geometry.call( this ); + + this.type = 'PolyhedronGeometry'; + + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; + + this.fromBufferGeometry( new PolyhedronBufferGeometry( vertices, indices, radius, detail ) ); + this.mergeVertices(); + + } + + PolyhedronGeometry.prototype = Object.create( Geometry.prototype ); + PolyhedronGeometry.prototype.constructor = PolyhedronGeometry; + + // PolyhedronBufferGeometry + + function PolyhedronBufferGeometry( vertices, indices, radius, detail ) { + + BufferGeometry.call( this ); + + this.type = 'PolyhedronBufferGeometry'; + + this.parameters = { + vertices: vertices, + indices: indices, + radius: radius, + detail: detail + }; + + radius = radius || 1; + detail = detail || 0; + + // default buffer data + + var vertexBuffer = []; + var uvBuffer = []; + + // the subdivision creates the vertex buffer data + + subdivide( detail ); + + // all vertices should lie on a conceptual sphere with a given radius + + appplyRadius( radius ); + + // finally, create the uv data + + generateUVs(); + + // build non-indexed geometry + + this.addAttribute( 'position', new Float32BufferAttribute( vertexBuffer, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( vertexBuffer.slice(), 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvBuffer, 2 ) ); + + if ( detail === 0 ) { + + this.computeVertexNormals(); // flat normals + + } else { + + this.normalizeNormals(); // smooth normals + + } + + // helper functions + + function subdivide( detail ) { + + var a = new Vector3(); + var b = new Vector3(); + var c = new Vector3(); + + // iterate over all faces and apply a subdivison with the given detail value + + for ( var i = 0; i < indices.length; i += 3 ) { + + // get the vertices of the face + + getVertexByIndex( indices[ i + 0 ], a ); + getVertexByIndex( indices[ i + 1 ], b ); + getVertexByIndex( indices[ i + 2 ], c ); + + // perform subdivision + + subdivideFace( a, b, c, detail ); + + } + + } + + function subdivideFace( a, b, c, detail ) { + + var cols = Math.pow( 2, detail ); + + // we use this multidimensional array as a data structure for creating the subdivision + + var v = []; + + var i, j; + + // construct all of the vertices for this subdivision + + for ( i = 0; i <= cols; i ++ ) { + + v[ i ] = []; + + var aj = a.clone().lerp( c, i / cols ); + var bj = b.clone().lerp( c, i / cols ); + + var rows = cols - i; + + for ( j = 0; j <= rows; j ++ ) { + + if ( j === 0 && i === cols ) { + + v[ i ][ j ] = aj; + + } else { + + v[ i ][ j ] = aj.clone().lerp( bj, j / rows ); + + } + + } + + } + + // construct all of the faces + + for ( i = 0; i < cols; i ++ ) { + + for ( j = 0; j < 2 * ( cols - i ) - 1; j ++ ) { + + var k = Math.floor( j / 2 ); + + if ( j % 2 === 0 ) { + + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + pushVertex( v[ i ][ k ] ); + + } else { + + pushVertex( v[ i ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k + 1 ] ); + pushVertex( v[ i + 1 ][ k ] ); + + } + + } + + } + + } + + function appplyRadius( radius ) { + + var vertex = new Vector3(); + + // iterate over the entire buffer and apply the radius to each vertex + + for ( var i = 0; i < vertexBuffer.length; i += 3 ) { + + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; + + vertex.normalize().multiplyScalar( radius ); + + vertexBuffer[ i + 0 ] = vertex.x; + vertexBuffer[ i + 1 ] = vertex.y; + vertexBuffer[ i + 2 ] = vertex.z; + + } + + } + + function generateUVs() { + + var vertex = new Vector3(); + + for ( var i = 0; i < vertexBuffer.length; i += 3 ) { + + vertex.x = vertexBuffer[ i + 0 ]; + vertex.y = vertexBuffer[ i + 1 ]; + vertex.z = vertexBuffer[ i + 2 ]; + + var u = azimuth( vertex ) / 2 / Math.PI + 0.5; + var v = inclination( vertex ) / Math.PI + 0.5; + uvBuffer.push( u, 1 - v ); + + } + + correctUVs(); + + correctSeam(); + + } + + function correctSeam() { + + // handle case when face straddles the seam, see #3269 + + for ( var i = 0; i < uvBuffer.length; i += 6 ) { + + // uv data of a single face + + var x0 = uvBuffer[ i + 0 ]; + var x1 = uvBuffer[ i + 2 ]; + var x2 = uvBuffer[ i + 4 ]; + + var max = Math.max( x0, x1, x2 ); + var min = Math.min( x0, x1, x2 ); + + // 0.9 is somewhat arbitrary + + if ( max > 0.9 && min < 0.1 ) { + + if ( x0 < 0.2 ) uvBuffer[ i + 0 ] += 1; + if ( x1 < 0.2 ) uvBuffer[ i + 2 ] += 1; + if ( x2 < 0.2 ) uvBuffer[ i + 4 ] += 1; + + } + + } + + } + + function pushVertex( vertex ) { + + vertexBuffer.push( vertex.x, vertex.y, vertex.z ); + + } + + function getVertexByIndex( index, vertex ) { + + var stride = index * 3; + + vertex.x = vertices[ stride + 0 ]; + vertex.y = vertices[ stride + 1 ]; + vertex.z = vertices[ stride + 2 ]; + + } + + function correctUVs() { + + var a = new Vector3(); + var b = new Vector3(); + var c = new Vector3(); + + var centroid = new Vector3(); + + var uvA = new Vector2(); + var uvB = new Vector2(); + var uvC = new Vector2(); + + for ( var i = 0, j = 0; i < vertexBuffer.length; i += 9, j += 6 ) { + + a.set( vertexBuffer[ i + 0 ], vertexBuffer[ i + 1 ], vertexBuffer[ i + 2 ] ); + b.set( vertexBuffer[ i + 3 ], vertexBuffer[ i + 4 ], vertexBuffer[ i + 5 ] ); + c.set( vertexBuffer[ i + 6 ], vertexBuffer[ i + 7 ], vertexBuffer[ i + 8 ] ); + + uvA.set( uvBuffer[ j + 0 ], uvBuffer[ j + 1 ] ); + uvB.set( uvBuffer[ j + 2 ], uvBuffer[ j + 3 ] ); + uvC.set( uvBuffer[ j + 4 ], uvBuffer[ j + 5 ] ); + + centroid.copy( a ).add( b ).add( c ).divideScalar( 3 ); + + var azi = azimuth( centroid ); + + correctUV( uvA, j + 0, a, azi ); + correctUV( uvB, j + 2, b, azi ); + correctUV( uvC, j + 4, c, azi ); + + } + + } + + function correctUV( uv, stride, vector, azimuth ) { + + if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) { + + uvBuffer[ stride ] = uv.x - 1; + + } + + if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) { + + uvBuffer[ stride ] = azimuth / 2 / Math.PI + 0.5; + + } + + } + + // Angle around the Y axis, counter-clockwise when looking from above. + + function azimuth( vector ) { + + return Math.atan2( vector.z, - vector.x ); + + } + + + // Angle above the XZ plane. + + function inclination( vector ) { + + return Math.atan2( - vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) ); + + } + + } + + PolyhedronBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + PolyhedronBufferGeometry.prototype.constructor = PolyhedronBufferGeometry; + + /** + * @author timothypratley / https://github.com/timothypratley + * @author Mugen87 / https://github.com/Mugen87 + */ + + // TetrahedronGeometry + + function TetrahedronGeometry( radius, detail ) { + + Geometry.call( this ); + + this.type = 'TetrahedronGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + this.fromBufferGeometry( new TetrahedronBufferGeometry( radius, detail ) ); + this.mergeVertices(); + + } + + TetrahedronGeometry.prototype = Object.create( Geometry.prototype ); + TetrahedronGeometry.prototype.constructor = TetrahedronGeometry; + + // TetrahedronBufferGeometry + + function TetrahedronBufferGeometry( radius, detail ) { + + var vertices = [ + 1, 1, 1, - 1, - 1, 1, - 1, 1, - 1, 1, - 1, - 1 + ]; + + var indices = [ + 2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1 + ]; + + PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); + + this.type = 'TetrahedronBufferGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + } + + TetrahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); + TetrahedronBufferGeometry.prototype.constructor = TetrahedronBufferGeometry; + + /** + * @author timothypratley / https://github.com/timothypratley + * @author Mugen87 / https://github.com/Mugen87 + */ + + // OctahedronGeometry + + function OctahedronGeometry( radius, detail ) { + + Geometry.call( this ); + + this.type = 'OctahedronGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + this.fromBufferGeometry( new OctahedronBufferGeometry( radius, detail ) ); + this.mergeVertices(); + + } + + OctahedronGeometry.prototype = Object.create( Geometry.prototype ); + OctahedronGeometry.prototype.constructor = OctahedronGeometry; + + // OctahedronBufferGeometry + + function OctahedronBufferGeometry( radius, detail ) { + + var vertices = [ + 1, 0, 0, - 1, 0, 0, 0, 1, 0, + 0, - 1, 0, 0, 0, 1, 0, 0, - 1 + ]; + + var indices = [ + 0, 2, 4, 0, 4, 3, 0, 3, 5, + 0, 5, 2, 1, 2, 5, 1, 5, 3, + 1, 3, 4, 1, 4, 2 + ]; + + PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); + + this.type = 'OctahedronBufferGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + } + + OctahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); + OctahedronBufferGeometry.prototype.constructor = OctahedronBufferGeometry; + + /** + * @author timothypratley / https://github.com/timothypratley + * @author Mugen87 / https://github.com/Mugen87 + */ + + // IcosahedronGeometry + + function IcosahedronGeometry( radius, detail ) { + + Geometry.call( this ); + + this.type = 'IcosahedronGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + this.fromBufferGeometry( new IcosahedronBufferGeometry( radius, detail ) ); + this.mergeVertices(); + + } + + IcosahedronGeometry.prototype = Object.create( Geometry.prototype ); + IcosahedronGeometry.prototype.constructor = IcosahedronGeometry; + + // IcosahedronBufferGeometry + + function IcosahedronBufferGeometry( radius, detail ) { + + var t = ( 1 + Math.sqrt( 5 ) ) / 2; + + var vertices = [ + - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, 0, + 0, - 1, t, 0, 1, t, 0, - 1, - t, 0, 1, - t, + t, 0, - 1, t, 0, 1, - t, 0, - 1, - t, 0, 1 + ]; + + var indices = [ + 0, 11, 5, 0, 5, 1, 0, 1, 7, 0, 7, 10, 0, 10, 11, + 1, 5, 9, 5, 11, 4, 11, 10, 2, 10, 7, 6, 7, 1, 8, + 3, 9, 4, 3, 4, 2, 3, 2, 6, 3, 6, 8, 3, 8, 9, + 4, 9, 5, 2, 4, 11, 6, 2, 10, 8, 6, 7, 9, 8, 1 + ]; + + PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); + + this.type = 'IcosahedronBufferGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + } + + IcosahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); + IcosahedronBufferGeometry.prototype.constructor = IcosahedronBufferGeometry; + + /** + * @author Abe Pazos / https://hamoid.com + * @author Mugen87 / https://github.com/Mugen87 + */ + + // DodecahedronGeometry + + function DodecahedronGeometry( radius, detail ) { + + Geometry.call( this ); + + this.type = 'DodecahedronGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + this.fromBufferGeometry( new DodecahedronBufferGeometry( radius, detail ) ); + this.mergeVertices(); + + } + + DodecahedronGeometry.prototype = Object.create( Geometry.prototype ); + DodecahedronGeometry.prototype.constructor = DodecahedronGeometry; + + // DodecahedronBufferGeometry + + function DodecahedronBufferGeometry( radius, detail ) { + + var t = ( 1 + Math.sqrt( 5 ) ) / 2; + var r = 1 / t; + + var vertices = [ + + // (±1, ±1, ±1) + - 1, - 1, - 1, - 1, - 1, 1, + - 1, 1, - 1, - 1, 1, 1, + 1, - 1, - 1, 1, - 1, 1, + 1, 1, - 1, 1, 1, 1, + + // (0, ±1/φ, ±φ) + 0, - r, - t, 0, - r, t, + 0, r, - t, 0, r, t, + + // (±1/φ, ±φ, 0) + - r, - t, 0, - r, t, 0, + r, - t, 0, r, t, 0, + + // (±φ, 0, ±1/φ) + - t, 0, - r, t, 0, - r, + - t, 0, r, t, 0, r + ]; + + var indices = [ + 3, 11, 7, 3, 7, 15, 3, 15, 13, + 7, 19, 17, 7, 17, 6, 7, 6, 15, + 17, 4, 8, 17, 8, 10, 17, 10, 6, + 8, 0, 16, 8, 16, 2, 8, 2, 10, + 0, 12, 1, 0, 1, 18, 0, 18, 16, + 6, 10, 2, 6, 2, 13, 6, 13, 15, + 2, 16, 18, 2, 18, 3, 2, 3, 13, + 18, 1, 9, 18, 9, 11, 18, 11, 3, + 4, 14, 12, 4, 12, 0, 4, 0, 8, + 11, 9, 5, 11, 5, 19, 11, 19, 7, + 19, 5, 14, 19, 14, 4, 19, 4, 17, + 1, 12, 14, 1, 14, 5, 1, 5, 9 + ]; + + PolyhedronBufferGeometry.call( this, vertices, indices, radius, detail ); + + this.type = 'DodecahedronBufferGeometry'; + + this.parameters = { + radius: radius, + detail: detail + }; + + } + + DodecahedronBufferGeometry.prototype = Object.create( PolyhedronBufferGeometry.prototype ); + DodecahedronBufferGeometry.prototype.constructor = DodecahedronBufferGeometry; + + /** + * @author oosmoxiecode / https://github.com/oosmoxiecode + * @author WestLangley / https://github.com/WestLangley + * @author zz85 / https://github.com/zz85 + * @author miningold / https://github.com/miningold + * @author jonobr1 / https://github.com/jonobr1 + * @author Mugen87 / https://github.com/Mugen87 + * + */ + + // TubeGeometry + + function TubeGeometry( path, tubularSegments, radius, radialSegments, closed, taper ) { + + Geometry.call( this ); + + this.type = 'TubeGeometry'; + + this.parameters = { + path: path, + tubularSegments: tubularSegments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; + + if ( taper !== undefined ) console.warn( 'THREE.TubeGeometry: taper has been removed.' ); + + var bufferGeometry = new TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ); + + // expose internals + + this.tangents = bufferGeometry.tangents; + this.normals = bufferGeometry.normals; + this.binormals = bufferGeometry.binormals; + + // create geometry + + this.fromBufferGeometry( bufferGeometry ); + this.mergeVertices(); + + } + + TubeGeometry.prototype = Object.create( Geometry.prototype ); + TubeGeometry.prototype.constructor = TubeGeometry; + + // TubeBufferGeometry + + function TubeBufferGeometry( path, tubularSegments, radius, radialSegments, closed ) { + + BufferGeometry.call( this ); + + this.type = 'TubeBufferGeometry'; + + this.parameters = { + path: path, + tubularSegments: tubularSegments, + radius: radius, + radialSegments: radialSegments, + closed: closed + }; + + tubularSegments = tubularSegments || 64; + radius = radius || 1; + radialSegments = radialSegments || 8; + closed = closed || false; + + var frames = path.computeFrenetFrames( tubularSegments, closed ); + + // expose internals + + this.tangents = frames.tangents; + this.normals = frames.normals; + this.binormals = frames.binormals; + + // helper variables + + var vertex = new Vector3(); + var normal = new Vector3(); + var uv = new Vector2(); + var P = new Vector3(); + + var i, j; + + // buffer + + var vertices = []; + var normals = []; + var uvs = []; + var indices = []; + + // create buffer data + + generateBufferData(); + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // functions + + function generateBufferData() { + + for ( i = 0; i < tubularSegments; i ++ ) { + + generateSegment( i ); + + } + + // if the geometry is not closed, generate the last row of vertices and normals + // at the regular position on the given path + // + // if the geometry is closed, duplicate the first row of vertices and normals (uvs will differ) + + generateSegment( ( closed === false ) ? tubularSegments : 0 ); + + // uvs are generated in a separate function. + // this makes it easy compute correct values for closed geometries + + generateUVs(); + + // finally create faces + + generateIndices(); + + } + + function generateSegment( i ) { + + // we use getPointAt to sample evenly distributed points from the given path + + P = path.getPointAt( i / tubularSegments, P ); + + // retrieve corresponding normal and binormal + + var N = frames.normals[ i ]; + var B = frames.binormals[ i ]; + + // generate normals and vertices for the current segment + + for ( j = 0; j <= radialSegments; j ++ ) { + + var v = j / radialSegments * Math.PI * 2; + + var sin = Math.sin( v ); + var cos = - Math.cos( v ); + + // normal + + normal.x = ( cos * N.x + sin * B.x ); + normal.y = ( cos * N.y + sin * B.y ); + normal.z = ( cos * N.z + sin * B.z ); + normal.normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // vertex + + vertex.x = P.x + radius * normal.x; + vertex.y = P.y + radius * normal.y; + vertex.z = P.z + radius * normal.z; + + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } + + function generateIndices() { + + for ( j = 1; j <= tubularSegments; j ++ ) { + + for ( i = 1; i <= radialSegments; i ++ ) { + + var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + var b = ( radialSegments + 1 ) * j + ( i - 1 ); + var c = ( radialSegments + 1 ) * j + i; + var d = ( radialSegments + 1 ) * ( j - 1 ) + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + } + + function generateUVs() { + + for ( i = 0; i <= tubularSegments; i ++ ) { + + for ( j = 0; j <= radialSegments; j ++ ) { + + uv.x = i / tubularSegments; + uv.y = j / radialSegments; + + uvs.push( uv.x, uv.y ); + + } + + } + + } + + } + + TubeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + TubeBufferGeometry.prototype.constructor = TubeBufferGeometry; + + /** + * @author oosmoxiecode + * @author Mugen87 / https://github.com/Mugen87 + * + * based on http://www.blackpawn.com/texts/pqtorus/ + */ + + // TorusKnotGeometry + + function TorusKnotGeometry( radius, tube, tubularSegments, radialSegments, p, q, heightScale ) { + + Geometry.call( this ); + + this.type = 'TorusKnotGeometry'; + + this.parameters = { + radius: radius, + tube: tube, + tubularSegments: tubularSegments, + radialSegments: radialSegments, + p: p, + q: q + }; + + if ( heightScale !== undefined ) console.warn( 'THREE.TorusKnotGeometry: heightScale has been deprecated. Use .scale( x, y, z ) instead.' ); + + this.fromBufferGeometry( new TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) ); + this.mergeVertices(); + + } + + TorusKnotGeometry.prototype = Object.create( Geometry.prototype ); + TorusKnotGeometry.prototype.constructor = TorusKnotGeometry; + + // TorusKnotBufferGeometry + + function TorusKnotBufferGeometry( radius, tube, tubularSegments, radialSegments, p, q ) { + + BufferGeometry.call( this ); + + this.type = 'TorusKnotBufferGeometry'; + + this.parameters = { + radius: radius, + tube: tube, + tubularSegments: tubularSegments, + radialSegments: radialSegments, + p: p, + q: q + }; + + radius = radius || 1; + tube = tube || 0.4; + tubularSegments = Math.floor( tubularSegments ) || 64; + radialSegments = Math.floor( radialSegments ) || 8; + p = p || 2; + q = q || 3; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var i, j; + + var vertex = new Vector3(); + var normal = new Vector3(); + + var P1 = new Vector3(); + var P2 = new Vector3(); + + var B = new Vector3(); + var T = new Vector3(); + var N = new Vector3(); + + // generate vertices, normals and uvs + + for ( i = 0; i <= tubularSegments; ++ i ) { + + // the radian "u" is used to calculate the position on the torus curve of the current tubular segement + + var u = i / tubularSegments * p * Math.PI * 2; + + // now we calculate two points. P1 is our current position on the curve, P2 is a little farther ahead. + // these points are used to create a special "coordinate space", which is necessary to calculate the correct vertex positions + + calculatePositionOnCurve( u, p, q, radius, P1 ); + calculatePositionOnCurve( u + 0.01, p, q, radius, P2 ); + + // calculate orthonormal basis + + T.subVectors( P2, P1 ); + N.addVectors( P2, P1 ); + B.crossVectors( T, N ); + N.crossVectors( B, T ); + + // normalize B, N. T can be ignored, we don't use it + + B.normalize(); + N.normalize(); + + for ( j = 0; j <= radialSegments; ++ j ) { + + // now calculate the vertices. they are nothing more than an extrusion of the torus curve. + // because we extrude a shape in the xy-plane, there is no need to calculate a z-value. + + var v = j / radialSegments * Math.PI * 2; + var cx = - tube * Math.cos( v ); + var cy = tube * Math.sin( v ); + + // now calculate the final vertex position. + // first we orient the extrusion with our basis vectos, then we add it to the current position on the curve + + vertex.x = P1.x + ( cx * N.x + cy * B.x ); + vertex.y = P1.y + ( cx * N.y + cy * B.y ); + vertex.z = P1.z + ( cx * N.z + cy * B.z ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal (P1 is always the center/origin of the extrusion, thus we can use it to calculate the normal) + + normal.subVectors( vertex, P1 ).normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); + + } + + } + + // generate indices + + for ( j = 1; j <= tubularSegments; j ++ ) { + + for ( i = 1; i <= radialSegments; i ++ ) { + + // indices + + var a = ( radialSegments + 1 ) * ( j - 1 ) + ( i - 1 ); + var b = ( radialSegments + 1 ) * j + ( i - 1 ); + var c = ( radialSegments + 1 ) * j + i; + var d = ( radialSegments + 1 ) * ( j - 1 ) + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // this function calculates the current position on the torus curve + + function calculatePositionOnCurve( u, p, q, radius, position ) { + + var cu = Math.cos( u ); + var su = Math.sin( u ); + var quOverP = q / p * u; + var cs = Math.cos( quOverP ); + + position.x = radius * ( 2 + cs ) * 0.5 * cu; + position.y = radius * ( 2 + cs ) * su * 0.5; + position.z = radius * Math.sin( quOverP ) * 0.5; + + } + + } + + TorusKnotBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + TorusKnotBufferGeometry.prototype.constructor = TorusKnotBufferGeometry; + + /** + * @author oosmoxiecode + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + // TorusGeometry + + function TorusGeometry( radius, tube, radialSegments, tubularSegments, arc ) { + + Geometry.call( this ); + + this.type = 'TorusGeometry'; + + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; + + this.fromBufferGeometry( new TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) ); + this.mergeVertices(); + + } + + TorusGeometry.prototype = Object.create( Geometry.prototype ); + TorusGeometry.prototype.constructor = TorusGeometry; + + // TorusBufferGeometry + + function TorusBufferGeometry( radius, tube, radialSegments, tubularSegments, arc ) { + + BufferGeometry.call( this ); + + this.type = 'TorusBufferGeometry'; + + this.parameters = { + radius: radius, + tube: tube, + radialSegments: radialSegments, + tubularSegments: tubularSegments, + arc: arc + }; + + radius = radius || 1; + tube = tube || 0.4; + radialSegments = Math.floor( radialSegments ) || 8; + tubularSegments = Math.floor( tubularSegments ) || 6; + arc = arc || Math.PI * 2; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var center = new Vector3(); + var vertex = new Vector3(); + var normal = new Vector3(); + + var j, i; + + // generate vertices, normals and uvs + + for ( j = 0; j <= radialSegments; j ++ ) { + + for ( i = 0; i <= tubularSegments; i ++ ) { + + var u = i / tubularSegments * arc; + var v = j / radialSegments * Math.PI * 2; + + // vertex + + vertex.x = ( radius + tube * Math.cos( v ) ) * Math.cos( u ); + vertex.y = ( radius + tube * Math.cos( v ) ) * Math.sin( u ); + vertex.z = tube * Math.sin( v ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + center.x = radius * Math.cos( u ); + center.y = radius * Math.sin( u ); + normal.subVectors( vertex, center ).normalize(); + + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( i / tubularSegments ); + uvs.push( j / radialSegments ); + + } + + } + + // generate indices + + for ( j = 1; j <= radialSegments; j ++ ) { + + for ( i = 1; i <= tubularSegments; i ++ ) { + + // indices + + var a = ( tubularSegments + 1 ) * j + i - 1; + var b = ( tubularSegments + 1 ) * ( j - 1 ) + i - 1; + var c = ( tubularSegments + 1 ) * ( j - 1 ) + i; + var d = ( tubularSegments + 1 ) * j + i; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + TorusBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + TorusBufferGeometry.prototype.constructor = TorusBufferGeometry; + + /** + * @author Mugen87 / https://github.com/Mugen87 + * Port from https://github.com/mapbox/earcut (v2.1.2) + */ + + var Earcut = { + + triangulate: function ( data, holeIndices, dim ) { + + dim = dim || 2; + + var hasHoles = holeIndices && holeIndices.length, + outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length, + outerNode = linkedList( data, 0, outerLen, dim, true ), + triangles = []; + + if ( ! outerNode ) return triangles; + + var minX, minY, maxX, maxY, x, y, invSize; + + if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim ); + + // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox + + if ( data.length > 80 * dim ) { + + minX = maxX = data[ 0 ]; + minY = maxY = data[ 1 ]; + + for ( var i = dim; i < outerLen; i += dim ) { + + x = data[ i ]; + y = data[ i + 1 ]; + if ( x < minX ) minX = x; + if ( y < minY ) minY = y; + if ( x > maxX ) maxX = x; + if ( y > maxY ) maxY = y; + + } + + // minX, minY and invSize are later used to transform coords into integers for z-order calculation + + invSize = Math.max( maxX - minX, maxY - minY ); + invSize = invSize !== 0 ? 1 / invSize : 0; + + } + + earcutLinked( outerNode, triangles, dim, minX, minY, invSize ); + + return triangles; + + } + + }; + + // create a circular doubly linked list from polygon points in the specified winding order + + function linkedList( data, start, end, dim, clockwise ) { + + var i, last; + + if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) { + + for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + + } else { + + for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last ); + + } + + if ( last && equals( last, last.next ) ) { + + removeNode( last ); + last = last.next; + + } + + return last; + + } + + // eliminate colinear or duplicate points + + function filterPoints( start, end ) { + + if ( ! start ) return start; + if ( ! end ) end = start; + + var p = start, again; + + do { + + again = false; + + if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) { + + removeNode( p ); + p = end = p.prev; + if ( p === p.next ) break; + again = true; + + } else { + + p = p.next; + + } + + } while ( again || p !== end ); + + return end; + + } + + // main ear slicing loop which triangulates a polygon (given as a linked list) + + function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) { + + if ( ! ear ) return; + + // interlink polygon nodes in z-order + + if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize ); + + var stop = ear, prev, next; + + // iterate through ears, slicing them one by one + + while ( ear.prev !== ear.next ) { + + prev = ear.prev; + next = ear.next; + + if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) { + + // cut off the triangle + triangles.push( prev.i / dim ); + triangles.push( ear.i / dim ); + triangles.push( next.i / dim ); + + removeNode( ear ); + + // skipping the next vertice leads to less sliver triangles + ear = next.next; + stop = next.next; + + continue; + + } + + ear = next; + + // if we looped through the whole remaining polygon and can't find any more ears + + if ( ear === stop ) { + + // try filtering points and slicing again + + if ( ! pass ) { + + earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 ); + + // if this didn't work, try curing all small self-intersections locally + + } else if ( pass === 1 ) { + + ear = cureLocalIntersections( ear, triangles, dim ); + earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 ); + + // as a last resort, try splitting the remaining polygon into two + + } else if ( pass === 2 ) { + + splitEarcut( ear, triangles, dim, minX, minY, invSize ); + + } + + break; + + } + + } + + } + + // check whether a polygon node forms a valid ear with adjacent nodes + + function isEar( ear ) { + + var a = ear.prev, + b = ear, + c = ear.next; + + if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + + // now make sure we don't have other points inside the potential ear + var p = ear.next.next; + + while ( p !== ear.prev ) { + + if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) { + + return false; + + } + + p = p.next; + + } + + return true; + + } + + function isEarHashed( ear, minX, minY, invSize ) { + + var a = ear.prev, + b = ear, + c = ear.next; + + if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear + + // triangle bbox; min & max are calculated like this for speed + + var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ), + minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ), + maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ), + maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y ); + + // z-order range for the current triangle bbox; + + var minZ = zOrder( minTX, minTY, minX, minY, invSize ), + maxZ = zOrder( maxTX, maxTY, minX, minY, invSize ); + + // first look for points inside the triangle in increasing z-order + + var p = ear.nextZ; + + while ( p && p.z <= maxZ ) { + + if ( p !== ear.prev && p !== ear.next && + pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && + area( p.prev, p, p.next ) >= 0 ) return false; + p = p.nextZ; + + } + + // then look for points in decreasing z-order + + p = ear.prevZ; + + while ( p && p.z >= minZ ) { + + if ( p !== ear.prev && p !== ear.next && + pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && + area( p.prev, p, p.next ) >= 0 ) return false; + + p = p.prevZ; + + } + + return true; + + } + + // go through all polygon nodes and cure small local self-intersections + + function cureLocalIntersections( start, triangles, dim ) { + + var p = start; + + do { + + var a = p.prev, b = p.next.next; + + if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) { + + triangles.push( a.i / dim ); + triangles.push( p.i / dim ); + triangles.push( b.i / dim ); + + // remove two nodes involved + + removeNode( p ); + removeNode( p.next ); + + p = start = b; + + } + + p = p.next; + + } while ( p !== start ); + + return p; + + } + + // try splitting polygon into two and triangulate them independently + + function splitEarcut( start, triangles, dim, minX, minY, invSize ) { + + // look for a valid diagonal that divides the polygon into two + + var a = start; + + do { + + var b = a.next.next; + + while ( b !== a.prev ) { + + if ( a.i !== b.i && isValidDiagonal( a, b ) ) { + + // split the polygon in two by the diagonal + + var c = splitPolygon( a, b ); + + // filter colinear points around the cuts + + a = filterPoints( a, a.next ); + c = filterPoints( c, c.next ); + + // run earcut on each half + + earcutLinked( a, triangles, dim, minX, minY, invSize ); + earcutLinked( c, triangles, dim, minX, minY, invSize ); + return; + + } + + b = b.next; + + } + + a = a.next; + + } while ( a !== start ); + + } + + // link every hole into the outer loop, producing a single-ring polygon without holes + + function eliminateHoles( data, holeIndices, outerNode, dim ) { + + var queue = [], i, len, start, end, list; + + for ( i = 0, len = holeIndices.length; i < len; i ++ ) { + + start = holeIndices[ i ] * dim; + end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length; + list = linkedList( data, start, end, dim, false ); + if ( list === list.next ) list.steiner = true; + queue.push( getLeftmost( list ) ); + + } + + queue.sort( compareX ); + + // process holes from left to right + + for ( i = 0; i < queue.length; i ++ ) { + + eliminateHole( queue[ i ], outerNode ); + outerNode = filterPoints( outerNode, outerNode.next ); + + } + + return outerNode; + + } + + function compareX( a, b ) { + + return a.x - b.x; + + } + + // find a bridge between vertices that connects hole with an outer ring and and link it + + function eliminateHole( hole, outerNode ) { + + outerNode = findHoleBridge( hole, outerNode ); + + if ( outerNode ) { + + var b = splitPolygon( outerNode, hole ); + + filterPoints( b, b.next ); + + } + + } + + // David Eberly's algorithm for finding a bridge between hole and outer polygon + + function findHoleBridge( hole, outerNode ) { + + var p = outerNode, + hx = hole.x, + hy = hole.y, + qx = - Infinity, + m; + + // find a segment intersected by a ray from the hole's leftmost point to the left; + // segment's endpoint with lesser x will be potential connection point + + do { + + if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) { + + var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y ); + + if ( x <= hx && x > qx ) { + + qx = x; + + if ( x === hx ) { + + if ( hy === p.y ) return p; + if ( hy === p.next.y ) return p.next; + + } + + m = p.x < p.next.x ? p : p.next; + + } + + } + + p = p.next; + + } while ( p !== outerNode ); + + if ( ! m ) return null; + + if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint + + // look for points inside the triangle of hole point, segment intersection and endpoint; + // if there are no points found, we have a valid connection; + // otherwise choose the point of the minimum angle with the ray as connection point + + var stop = m, + mx = m.x, + my = m.y, + tanMin = Infinity, + tan; + + p = m.next; + + while ( p !== stop ) { + + if ( hx >= p.x && p.x >= mx && hx !== p.x && + pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) { + + tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential + + if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) { + + m = p; + tanMin = tan; + + } + + } + + p = p.next; + + } + + return m; + + } + + // interlink polygon nodes in z-order + + function indexCurve( start, minX, minY, invSize ) { + + var p = start; + + do { + + if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize ); + p.prevZ = p.prev; + p.nextZ = p.next; + p = p.next; + + } while ( p !== start ); + + p.prevZ.nextZ = null; + p.prevZ = null; + + sortLinked( p ); + + } + + // Simon Tatham's linked list merge sort algorithm + // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html + + function sortLinked( list ) { + + var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1; + + do { + + p = list; + list = null; + tail = null; + numMerges = 0; + + while ( p ) { + + numMerges ++; + q = p; + pSize = 0; + + for ( i = 0; i < inSize; i ++ ) { + + pSize ++; + q = q.nextZ; + if ( ! q ) break; + + } + + qSize = inSize; + + while ( pSize > 0 || ( qSize > 0 && q ) ) { + + if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) { + + e = p; + p = p.nextZ; + pSize --; + + } else { + + e = q; + q = q.nextZ; + qSize --; + + } + + if ( tail ) tail.nextZ = e; + else list = e; + + e.prevZ = tail; + tail = e; + + } + + p = q; + + } + + tail.nextZ = null; + inSize *= 2; + + } while ( numMerges > 1 ); + + return list; + + } + + // z-order of a point given coords and inverse of the longer side of data bbox + + function zOrder( x, y, minX, minY, invSize ) { + + // coords are transformed into non-negative 15-bit integer range + + x = 32767 * ( x - minX ) * invSize; + y = 32767 * ( y - minY ) * invSize; + + x = ( x | ( x << 8 ) ) & 0x00FF00FF; + x = ( x | ( x << 4 ) ) & 0x0F0F0F0F; + x = ( x | ( x << 2 ) ) & 0x33333333; + x = ( x | ( x << 1 ) ) & 0x55555555; + + y = ( y | ( y << 8 ) ) & 0x00FF00FF; + y = ( y | ( y << 4 ) ) & 0x0F0F0F0F; + y = ( y | ( y << 2 ) ) & 0x33333333; + y = ( y | ( y << 1 ) ) & 0x55555555; + + return x | ( y << 1 ); + + } + + // find the leftmost node of a polygon ring + + function getLeftmost( start ) { + + var p = start, leftmost = start; + + do { + + if ( p.x < leftmost.x ) leftmost = p; + p = p.next; + + } while ( p !== start ); + + return leftmost; + + } + + // check if a point lies within a convex triangle + + function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) { + + return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 && + ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 && + ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0; + + } + + // check if a diagonal between two polygon nodes is valid (lies in polygon interior) + + function isValidDiagonal( a, b ) { + + return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) && + locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b ); + + } + + // signed area of a triangle + + function area( p, q, r ) { + + return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y ); + + } + + // check if two points are equal + + function equals( p1, p2 ) { + + return p1.x === p2.x && p1.y === p2.y; + + } + + // check if two segments intersect + + function intersects( p1, q1, p2, q2 ) { + + if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) || + ( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true; + + return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 && + area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0; + + } + + // check if a polygon diagonal intersects any polygon segments + + function intersectsPolygon( a, b ) { + + var p = a; + + do { + + if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && + intersects( p, p.next, a, b ) ) { + + return true; + + } + + p = p.next; + + } while ( p !== a ); + + return false; + + } + + // check if a polygon diagonal is locally inside the polygon + + function locallyInside( a, b ) { + + return area( a.prev, a, a.next ) < 0 ? + area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 : + area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0; + + } + + // check if the middle point of a polygon diagonal is inside the polygon + + function middleInside( a, b ) { + + var p = a, + inside = false, + px = ( a.x + b.x ) / 2, + py = ( a.y + b.y ) / 2; + + do { + + if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y && + ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) { + + inside = ! inside; + + } + + p = p.next; + + } while ( p !== a ); + + return inside; + + } + + // link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two; + // if one belongs to the outer ring and another to a hole, it merges it into a single ring + + function splitPolygon( a, b ) { + + var a2 = new Node( a.i, a.x, a.y ), + b2 = new Node( b.i, b.x, b.y ), + an = a.next, + bp = b.prev; + + a.next = b; + b.prev = a; + + a2.next = an; + an.prev = a2; + + b2.next = a2; + a2.prev = b2; + + bp.next = b2; + b2.prev = bp; + + return b2; + + } + + // create a node and optionally link it with previous one (in a circular doubly linked list) + + function insertNode( i, x, y, last ) { + + var p = new Node( i, x, y ); + + if ( ! last ) { + + p.prev = p; + p.next = p; + + } else { + + p.next = last.next; + p.prev = last; + last.next.prev = p; + last.next = p; + + } + + return p; + + } + + function removeNode( p ) { + + p.next.prev = p.prev; + p.prev.next = p.next; + + if ( p.prevZ ) p.prevZ.nextZ = p.nextZ; + if ( p.nextZ ) p.nextZ.prevZ = p.prevZ; + + } + + function Node( i, x, y ) { + + // vertice index in coordinates array + this.i = i; + + // vertex coordinates + this.x = x; + this.y = y; + + // previous and next vertice nodes in a polygon ring + this.prev = null; + this.next = null; + + // z-order curve value + this.z = null; + + // previous and next nodes in z-order + this.prevZ = null; + this.nextZ = null; + + // indicates whether this is a steiner point + this.steiner = false; + + } + + function signedArea( data, start, end, dim ) { + + var sum = 0; + + for ( var i = start, j = end - dim; i < end; i += dim ) { + + sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] ); + j = i; + + } + + return sum; + + } + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + */ + + var ShapeUtils = { + + // calculate area of the contour polygon + + area: function ( contour ) { + + var n = contour.length; + var a = 0.0; + + for ( var p = n - 1, q = 0; q < n; p = q ++ ) { + + a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y; + + } + + return a * 0.5; + + }, + + isClockWise: function ( pts ) { + + return ShapeUtils.area( pts ) < 0; + + }, + + triangulateShape: function ( contour, holes ) { + + var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ] + var holeIndices = []; // array of hole indices + var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ] + + removeDupEndPts( contour ); + addContour( vertices, contour ); + + // + + var holeIndex = contour.length; + + holes.forEach( removeDupEndPts ); + + for ( var i = 0; i < holes.length; i ++ ) { + + holeIndices.push( holeIndex ); + holeIndex += holes[ i ].length; + addContour( vertices, holes[ i ] ); + + } + + // + + var triangles = Earcut.triangulate( vertices, holeIndices ); + + // + + for ( var i = 0; i < triangles.length; i += 3 ) { + + faces.push( triangles.slice( i, i + 3 ) ); + + } + + return faces; + + } + + }; + + function removeDupEndPts( points ) { + + var l = points.length; + + if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) { + + points.pop(); + + } + + } + + function addContour( vertices, contour ) { + + for ( var i = 0; i < contour.length; i ++ ) { + + vertices.push( contour[ i ].x ); + vertices.push( contour[ i ].y ); + + } + + } + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + * Creates extruded geometry from a path shape. + * + * parameters = { + * + * curveSegments: , // number of points on the curves + * steps: , // number of points for z-side extrusions / used for subdividing segments of extrude spline too + * amount: , // Depth to extrude the shape + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into the original shape bevel goes + * bevelSize: , // how far from shape outline is bevel + * bevelSegments: , // number of bevel layers + * + * extrudePath: // curve to extrude shape along + * frames: // containing arrays of tangents, normals, binormals + * + * UVGenerator: // object that provides UV generator functions + * + * } + */ + + // ExtrudeGeometry + + function ExtrudeGeometry( shapes, options ) { + + Geometry.call( this ); + + this.type = 'ExtrudeGeometry'; + + this.parameters = { + shapes: shapes, + options: options + }; + + this.fromBufferGeometry( new ExtrudeBufferGeometry( shapes, options ) ); + this.mergeVertices(); + + } + + ExtrudeGeometry.prototype = Object.create( Geometry.prototype ); + ExtrudeGeometry.prototype.constructor = ExtrudeGeometry; + + // ExtrudeBufferGeometry + + function ExtrudeBufferGeometry( shapes, options ) { + + if ( typeof ( shapes ) === "undefined" ) { + + return; + + } + + BufferGeometry.call( this ); + + this.type = 'ExtrudeBufferGeometry'; + + shapes = Array.isArray( shapes ) ? shapes : [ shapes ]; + + this.addShapeList( shapes, options ); + + this.computeVertexNormals(); + + // can't really use automatic vertex normals + // as then front and back sides get smoothed too + // should do separate smoothing just for sides + + //this.computeVertexNormals(); + + //console.log( "took", ( Date.now() - startTime ) ); + + } + + ExtrudeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + ExtrudeBufferGeometry.prototype.constructor = ExtrudeBufferGeometry; + + ExtrudeBufferGeometry.prototype.getArrays = function () { + + var positionAttribute = this.getAttribute( "position" ); + var verticesArray = positionAttribute ? Array.prototype.slice.call( positionAttribute.array ) : []; + + var uvAttribute = this.getAttribute( "uv" ); + var uvArray = uvAttribute ? Array.prototype.slice.call( uvAttribute.array ) : []; + + var IndexAttribute = this.index; + var indicesArray = IndexAttribute ? Array.prototype.slice.call( IndexAttribute.array ) : []; + + return { + position: verticesArray, + uv: uvArray, + index: indicesArray + }; + + }; + + ExtrudeBufferGeometry.prototype.addShapeList = function ( shapes, options ) { + + var sl = shapes.length; + options.arrays = this.getArrays(); + + for ( var s = 0; s < sl; s ++ ) { + + var shape = shapes[ s ]; + this.addShape( shape, options ); + + } + + this.setIndex( options.arrays.index ); + this.addAttribute( 'position', new Float32BufferAttribute( options.arrays.position, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) ); + + }; + + ExtrudeBufferGeometry.prototype.addShape = function ( shape, options ) { + + var arrays = options.arrays ? options.arrays : this.getArrays(); + var verticesArray = arrays.position; + var indicesArray = arrays.index; + var uvArray = arrays.uv; + + var placeholder = []; + + + var amount = options.amount !== undefined ? options.amount : 100; + + var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10 + var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8 + var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3; + + var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false + + var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12; + + var steps = options.steps !== undefined ? options.steps : 1; + + var extrudePath = options.extrudePath; + var extrudePts, extrudeByPath = false; + + // Use default WorldUVGenerator if no UV generators are specified. + var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : ExtrudeGeometry.WorldUVGenerator; + + var splineTube, binormal, normal, position2; + if ( extrudePath ) { + + extrudePts = extrudePath.getSpacedPoints( steps ); + + extrudeByPath = true; + bevelEnabled = false; // bevels not supported for path extrusion + + // SETUP TNB variables + + // TODO1 - have a .isClosed in spline? + + splineTube = options.frames !== undefined ? options.frames : extrudePath.computeFrenetFrames( steps, false ); + + // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length); + + binormal = new Vector3(); + normal = new Vector3(); + position2 = new Vector3(); + + } + + // Safeguards if bevels are not enabled + + if ( ! bevelEnabled ) { + + bevelSegments = 0; + bevelThickness = 0; + bevelSize = 0; + + } + + // Variables initialization + + var ahole, h, hl; // looping of holes + var scope = this; + + var shapePoints = shape.extractPoints( curveSegments ); + + var vertices = shapePoints.shape; + var holes = shapePoints.holes; + + var reverse = ! ShapeUtils.isClockWise( vertices ); + + if ( reverse ) { + + vertices = vertices.reverse(); + + // Maybe we should also check if holes are in the opposite direction, just to be safe ... + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + if ( ShapeUtils.isClockWise( ahole ) ) { + + holes[ h ] = ahole.reverse(); + + } + + } + + } + + + var faces = ShapeUtils.triangulateShape( vertices, holes ); + + /* Vertices */ + + var contour = vertices; // vertices has all points but contour has only points of circumference + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + vertices = vertices.concat( ahole ); + + } + + + function scalePt2( pt, vec, size ) { + + if ( ! vec ) console.error( "THREE.ExtrudeGeometry: vec does not exist" ); + + return vec.clone().multiplyScalar( size ).add( pt ); + + } + + var b, bs, t, z, + vert, vlen = vertices.length, + face, flen = faces.length; + + + // Find directions for point movement + + + function getBevelVec( inPt, inPrev, inNext ) { + + // computes for inPt the corresponding point inPt' on a new contour + // shifted by 1 unit (length of normalized vector) to the left + // if we walk along contour clockwise, this new contour is outside the old one + // + // inPt' is the intersection of the two lines parallel to the two + // adjacent edges of inPt at a distance of 1 unit on the left side. + + var v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt + + // good reading for geometry algorithms (here: line-line intersection) + // http://geomalgorithms.com/a05-_intersect-1.html + + var v_prev_x = inPt.x - inPrev.x, + v_prev_y = inPt.y - inPrev.y; + var v_next_x = inNext.x - inPt.x, + v_next_y = inNext.y - inPt.y; + + var v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y ); + + // check for collinear edges + var collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + if ( Math.abs( collinear0 ) > Number.EPSILON ) { + + // not collinear + + // length of vectors for normalizing + + var v_prev_len = Math.sqrt( v_prev_lensq ); + var v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y ); + + // shift adjacent points by unit vectors to the left + + var ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len ); + var ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len ); + + var ptNextShift_x = ( inNext.x - v_next_y / v_next_len ); + var ptNextShift_y = ( inNext.y + v_next_x / v_next_len ); + + // scaling factor for v_prev to intersection point + + var sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y - + ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) / + ( v_prev_x * v_next_y - v_prev_y * v_next_x ); + + // vector from inPt to intersection point + + v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x ); + v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y ); + + // Don't normalize!, otherwise sharp corners become ugly + // but prevent crazy spikes + var v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y ); + if ( v_trans_lensq <= 2 ) { + + return new Vector2( v_trans_x, v_trans_y ); + + } else { + + shrink_by = Math.sqrt( v_trans_lensq / 2 ); + + } + + } else { + + // handle special case of collinear edges + + var direction_eq = false; // assumes: opposite + if ( v_prev_x > Number.EPSILON ) { + + if ( v_next_x > Number.EPSILON ) { + + direction_eq = true; + + } + + } else { + + if ( v_prev_x < - Number.EPSILON ) { + + if ( v_next_x < - Number.EPSILON ) { + + direction_eq = true; + + } + + } else { + + if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) { + + direction_eq = true; + + } + + } + + } + + if ( direction_eq ) { + + // console.log("Warning: lines are a straight sequence"); + v_trans_x = - v_prev_y; + v_trans_y = v_prev_x; + shrink_by = Math.sqrt( v_prev_lensq ); + + } else { + + // console.log("Warning: lines are a straight spike"); + v_trans_x = v_prev_x; + v_trans_y = v_prev_y; + shrink_by = Math.sqrt( v_prev_lensq / 2 ); + + } + + } + + return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by ); + + } + + + var contourMovements = []; + + for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + // console.log('i,j,k', i, j , k) + + contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] ); + + } + + var holesMovements = [], + oneHoleMovements, verticesMovements = contourMovements.concat(); + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + + oneHoleMovements = []; + + for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) { + + if ( j === il ) j = 0; + if ( k === il ) k = 0; + + // (j)---(i)---(k) + oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] ); + + } + + holesMovements.push( oneHoleMovements ); + verticesMovements = verticesMovements.concat( oneHoleMovements ); + + } + + + // Loop bevelSegments, 1 for the front, 1 for the back + + for ( b = 0; b < bevelSegments; b ++ ) { + + //for ( b = bevelSegments; b > 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * Math.cos( t * Math.PI / 2 ); + bs = bevelSize * Math.sin( t * Math.PI / 2 ); + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i ++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + v( vert.x, vert.y, - z ); + + } + + } + + } + + bs = bevelSize; + + // Back facing vertices + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, 0 ); + + } else { + + // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x ); + + normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + // Add stepped vertices... + // Including front facing vertices + + var s; + + for ( s = 1; s <= steps; s ++ ) { + + for ( i = 0; i < vlen; i ++ ) { + + vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ]; + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, amount / steps * s ); + + } else { + + // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x ); + + normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x ); + binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y ); + + position2.copy( extrudePts[ s ] ).add( normal ).add( binormal ); + + v( position2.x, position2.y, position2.z ); + + } + + } + + } + + + // Add bevel segments planes + + //for ( b = 1; b <= bevelSegments; b ++ ) { + for ( b = bevelSegments - 1; b >= 0; b -- ) { + + t = b / bevelSegments; + z = bevelThickness * Math.cos( t * Math.PI / 2 ); + bs = bevelSize * Math.sin( t * Math.PI / 2 ); + + // contract shape + + for ( i = 0, il = contour.length; i < il; i ++ ) { + + vert = scalePt2( contour[ i ], contourMovements[ i ], bs ); + v( vert.x, vert.y, amount + z ); + + } + + // expand holes + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + oneHoleMovements = holesMovements[ h ]; + + for ( i = 0, il = ahole.length; i < il; i ++ ) { + + vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs ); + + if ( ! extrudeByPath ) { + + v( vert.x, vert.y, amount + z ); + + } else { + + v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z ); + + } + + } + + } + + } + + /* Faces */ + + // Top and bottom faces + + buildLidFaces(); + + // Sides faces + + buildSideFaces(); + + + ///// Internal functions + + function buildLidFaces() { + + var start = verticesArray.length / 3; + + if ( bevelEnabled ) { + + var layer = 0; // steps + 1 + var offset = vlen * layer; + + // Bottom faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset ); + + } + + layer = steps + bevelSegments * 2; + offset = vlen * layer; + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset ); + + } + + } else { + + // Bottom faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 2 ], face[ 1 ], face[ 0 ] ); + + } + + // Top faces + + for ( i = 0; i < flen; i ++ ) { + + face = faces[ i ]; + f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps ); + + } + + } + + scope.addGroup( start, verticesArray.length / 3 - start, options.material !== undefined ? options.material : 0 ); + + } + + // Create faces for the z-sides of the shape + + function buildSideFaces() { + + var start = verticesArray.length / 3; + var layeroffset = 0; + sidewalls( contour, layeroffset ); + layeroffset += contour.length; + + for ( h = 0, hl = holes.length; h < hl; h ++ ) { + + ahole = holes[ h ]; + sidewalls( ahole, layeroffset ); + + //, true + layeroffset += ahole.length; + + } + + + scope.addGroup( start, verticesArray.length / 3 - start, options.extrudeMaterial !== undefined ? options.extrudeMaterial : 1 ); + + + } + + function sidewalls( contour, layeroffset ) { + + var j, k; + i = contour.length; + + while ( -- i >= 0 ) { + + j = i; + k = i - 1; + if ( k < 0 ) k = contour.length - 1; + + //console.log('b', i,j, i-1, k,vertices.length); + + var s = 0, + sl = steps + bevelSegments * 2; + + for ( s = 0; s < sl; s ++ ) { + + var slen1 = vlen * s; + var slen2 = vlen * ( s + 1 ); + + var a = layeroffset + j + slen1, + b = layeroffset + k + slen1, + c = layeroffset + k + slen2, + d = layeroffset + j + slen2; + + f4( a, b, c, d ); + + } + + } + + } + + function v( x, y, z ) { + + placeholder.push( x ); + placeholder.push( y ); + placeholder.push( z ); + + } + + + function f3( a, b, c ) { + + addVertex( a ); + addVertex( b ); + addVertex( c ); + + var nextIndex = verticesArray.length / 3; + var uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + + } + + function f4( a, b, c, d ) { + + addVertex( a ); + addVertex( b ); + addVertex( d ); + + addVertex( b ); + addVertex( c ); + addVertex( d ); + + + var nextIndex = verticesArray.length / 3; + var uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 ); + + addUV( uvs[ 0 ] ); + addUV( uvs[ 1 ] ); + addUV( uvs[ 3 ] ); + + addUV( uvs[ 1 ] ); + addUV( uvs[ 2 ] ); + addUV( uvs[ 3 ] ); + + } + + function addVertex( index ) { + + indicesArray.push( verticesArray.length / 3 ); + verticesArray.push( placeholder[ index * 3 + 0 ] ); + verticesArray.push( placeholder[ index * 3 + 1 ] ); + verticesArray.push( placeholder[ index * 3 + 2 ] ); + + } + + + function addUV( vector2 ) { + + uvArray.push( vector2.x ); + uvArray.push( vector2.y ); + + } + + if ( ! options.arrays ) { + + this.setIndex( indicesArray ); + this.addAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) ); + + } + + }; + + ExtrudeGeometry.WorldUVGenerator = { + + generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) { + + var a_x = vertices[ indexA * 3 ]; + var a_y = vertices[ indexA * 3 + 1 ]; + var b_x = vertices[ indexB * 3 ]; + var b_y = vertices[ indexB * 3 + 1 ]; + var c_x = vertices[ indexC * 3 ]; + var c_y = vertices[ indexC * 3 + 1 ]; + + return [ + new Vector2( a_x, a_y ), + new Vector2( b_x, b_y ), + new Vector2( c_x, c_y ) + ]; + + }, + + generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) { + + var a_x = vertices[ indexA * 3 ]; + var a_y = vertices[ indexA * 3 + 1 ]; + var a_z = vertices[ indexA * 3 + 2 ]; + var b_x = vertices[ indexB * 3 ]; + var b_y = vertices[ indexB * 3 + 1 ]; + var b_z = vertices[ indexB * 3 + 2 ]; + var c_x = vertices[ indexC * 3 ]; + var c_y = vertices[ indexC * 3 + 1 ]; + var c_z = vertices[ indexC * 3 + 2 ]; + var d_x = vertices[ indexD * 3 ]; + var d_y = vertices[ indexD * 3 + 1 ]; + var d_z = vertices[ indexD * 3 + 2 ]; + + if ( Math.abs( a_y - b_y ) < 0.01 ) { + + return [ + new Vector2( a_x, 1 - a_z ), + new Vector2( b_x, 1 - b_z ), + new Vector2( c_x, 1 - c_z ), + new Vector2( d_x, 1 - d_z ) + ]; + + } else { + + return [ + new Vector2( a_y, 1 - a_z ), + new Vector2( b_y, 1 - b_z ), + new Vector2( c_y, 1 - c_z ), + new Vector2( d_y, 1 - d_z ) + ]; + + } + + } + }; + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author alteredq / http://alteredqualia.com/ + * + * Text = 3D Text + * + * parameters = { + * font: , // font + * + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: // how far from text outline is bevel + * } + */ + + // TextGeometry + + function TextGeometry( text, parameters ) { + + Geometry.call( this ); + + this.type = 'TextGeometry'; + + this.parameters = { + text: text, + parameters: parameters + }; + + this.fromBufferGeometry( new TextBufferGeometry( text, parameters ) ); + this.mergeVertices(); + + } + + TextGeometry.prototype = Object.create( Geometry.prototype ); + TextGeometry.prototype.constructor = TextGeometry; + + // TextBufferGeometry + + function TextBufferGeometry( text, parameters ) { + + parameters = parameters || {}; + + var font = parameters.font; + + if ( ! ( font && font.isFont ) ) { + + console.error( 'THREE.TextGeometry: font parameter is not an instance of THREE.Font.' ); + return new Geometry(); + + } + + var shapes = font.generateShapes( text, parameters.size, parameters.curveSegments ); + + // translate parameters to ExtrudeGeometry API + + parameters.amount = parameters.height !== undefined ? parameters.height : 50; + + // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + + ExtrudeBufferGeometry.call( this, shapes, parameters ); + + this.type = 'TextBufferGeometry'; + + } + + TextBufferGeometry.prototype = Object.create( ExtrudeBufferGeometry.prototype ); + TextBufferGeometry.prototype.constructor = TextBufferGeometry; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author benaadams / https://twitter.com/ben_a_adams + * @author Mugen87 / https://github.com/Mugen87 + */ + + // SphereGeometry + + function SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + + Geometry.call( this ); + + this.type = 'SphereGeometry'; + + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + this.fromBufferGeometry( new SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) ); + this.mergeVertices(); + + } + + SphereGeometry.prototype = Object.create( Geometry.prototype ); + SphereGeometry.prototype.constructor = SphereGeometry; + + // SphereBufferGeometry + + function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) { + + BufferGeometry.call( this ); + + this.type = 'SphereBufferGeometry'; + + this.parameters = { + radius: radius, + widthSegments: widthSegments, + heightSegments: heightSegments, + phiStart: phiStart, + phiLength: phiLength, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + radius = radius || 1; + + widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 ); + heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 ); + + phiStart = phiStart !== undefined ? phiStart : 0; + phiLength = phiLength !== undefined ? phiLength : Math.PI * 2; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI; + + var thetaEnd = thetaStart + thetaLength; + + var ix, iy; + + var index = 0; + var grid = []; + + var vertex = new Vector3(); + var normal = new Vector3(); + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // generate vertices, normals and uvs + + for ( iy = 0; iy <= heightSegments; iy ++ ) { + + var verticesRow = []; + + var v = iy / heightSegments; + + for ( ix = 0; ix <= widthSegments; ix ++ ) { + + var u = ix / widthSegments; + + // vertex + + vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + vertex.y = radius * Math.cos( thetaStart + v * thetaLength ); + vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normal.set( vertex.x, vertex.y, vertex.z ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, 1 - v ); + + verticesRow.push( index ++ ); + + } + + grid.push( verticesRow ); + + } + + // indices + + for ( iy = 0; iy < heightSegments; iy ++ ) { + + for ( ix = 0; ix < widthSegments; ix ++ ) { + + var a = grid[ iy ][ ix + 1 ]; + var b = grid[ iy ][ ix ]; + var c = grid[ iy + 1 ][ ix ]; + var d = grid[ iy + 1 ][ ix + 1 ]; + + if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d ); + if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + SphereBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + SphereBufferGeometry.prototype.constructor = SphereBufferGeometry; + + /** + * @author Kaleb Murphy + * @author Mugen87 / https://github.com/Mugen87 + */ + + // RingGeometry + + function RingGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + + Geometry.call( this ); + + this.type = 'RingGeometry'; + + this.parameters = { + innerRadius: innerRadius, + outerRadius: outerRadius, + thetaSegments: thetaSegments, + phiSegments: phiSegments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + this.fromBufferGeometry( new RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) ); + this.mergeVertices(); + + } + + RingGeometry.prototype = Object.create( Geometry.prototype ); + RingGeometry.prototype.constructor = RingGeometry; + + // RingBufferGeometry + + function RingBufferGeometry( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) { + + BufferGeometry.call( this ); + + this.type = 'RingBufferGeometry'; + + this.parameters = { + innerRadius: innerRadius, + outerRadius: outerRadius, + thetaSegments: thetaSegments, + phiSegments: phiSegments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + innerRadius = innerRadius || 0.5; + outerRadius = outerRadius || 1; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8; + phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 1; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // some helper variables + + var segment; + var radius = innerRadius; + var radiusStep = ( ( outerRadius - innerRadius ) / phiSegments ); + var vertex = new Vector3(); + var uv = new Vector2(); + var j, i; + + // generate vertices, normals and uvs + + for ( j = 0; j <= phiSegments; j ++ ) { + + for ( i = 0; i <= thetaSegments; i ++ ) { + + // values are generate from the inside of the ring to the outside + + segment = thetaStart + i / thetaSegments * thetaLength; + + // vertex + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, 0, 1 ); + + // uv + + uv.x = ( vertex.x / outerRadius + 1 ) / 2; + uv.y = ( vertex.y / outerRadius + 1 ) / 2; + + uvs.push( uv.x, uv.y ); + + } + + // increase the radius for next row of vertices + + radius += radiusStep; + + } + + // indices + + for ( j = 0; j < phiSegments; j ++ ) { + + var thetaSegmentLevel = j * ( thetaSegments + 1 ); + + for ( i = 0; i < thetaSegments; i ++ ) { + + segment = i + thetaSegmentLevel; + + var a = segment; + var b = segment + thetaSegments + 1; + var c = segment + thetaSegments + 2; + var d = segment + 1; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + RingBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + RingBufferGeometry.prototype.constructor = RingBufferGeometry; + + /** + * @author astrodud / http://astrodud.isgreat.org/ + * @author zz85 / https://github.com/zz85 + * @author bhouston / http://clara.io + * @author Mugen87 / https://github.com/Mugen87 + */ + + // LatheGeometry + + function LatheGeometry( points, segments, phiStart, phiLength ) { + + Geometry.call( this ); + + this.type = 'LatheGeometry'; + + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; + + this.fromBufferGeometry( new LatheBufferGeometry( points, segments, phiStart, phiLength ) ); + this.mergeVertices(); + + } + + LatheGeometry.prototype = Object.create( Geometry.prototype ); + LatheGeometry.prototype.constructor = LatheGeometry; + + // LatheBufferGeometry + + function LatheBufferGeometry( points, segments, phiStart, phiLength ) { + + BufferGeometry.call( this ); + + this.type = 'LatheBufferGeometry'; + + this.parameters = { + points: points, + segments: segments, + phiStart: phiStart, + phiLength: phiLength + }; + + segments = Math.floor( segments ) || 12; + phiStart = phiStart || 0; + phiLength = phiLength || Math.PI * 2; + + // clamp phiLength so it's in range of [ 0, 2PI ] + + phiLength = _Math.clamp( phiLength, 0, Math.PI * 2 ); + + + // buffers + + var indices = []; + var vertices = []; + var uvs = []; + + // helper variables + + var base; + var inverseSegments = 1.0 / segments; + var vertex = new Vector3(); + var uv = new Vector2(); + var i, j; + + // generate vertices and uvs + + for ( i = 0; i <= segments; i ++ ) { + + var phi = phiStart + i * inverseSegments * phiLength; + + var sin = Math.sin( phi ); + var cos = Math.cos( phi ); + + for ( j = 0; j <= ( points.length - 1 ); j ++ ) { + + // vertex + + vertex.x = points[ j ].x * sin; + vertex.y = points[ j ].y; + vertex.z = points[ j ].x * cos; + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // uv + + uv.x = i / segments; + uv.y = j / ( points.length - 1 ); + + uvs.push( uv.x, uv.y ); + + + } + + } + + // indices + + for ( i = 0; i < segments; i ++ ) { + + for ( j = 0; j < ( points.length - 1 ); j ++ ) { + + base = j + i * points.length; + + var a = base; + var b = base + points.length; + var c = base + points.length + 1; + var d = base + 1; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // generate normals + + this.computeVertexNormals(); + + // if the geometry is closed, we need to average the normals along the seam. + // because the corresponding vertices are identical (but still have different UVs). + + if ( phiLength === Math.PI * 2 ) { + + var normals = this.attributes.normal.array; + var n1 = new Vector3(); + var n2 = new Vector3(); + var n = new Vector3(); + + // this is the buffer offset for the last line of vertices + + base = segments * points.length * 3; + + for ( i = 0, j = 0; i < points.length; i ++, j += 3 ) { + + // select the normal of the vertex in the first line + + n1.x = normals[ j + 0 ]; + n1.y = normals[ j + 1 ]; + n1.z = normals[ j + 2 ]; + + // select the normal of the vertex in the last line + + n2.x = normals[ base + j + 0 ]; + n2.y = normals[ base + j + 1 ]; + n2.z = normals[ base + j + 2 ]; + + // average normals + + n.addVectors( n1, n2 ).normalize(); + + // assign the new values to both normals + + normals[ j + 0 ] = normals[ base + j + 0 ] = n.x; + normals[ j + 1 ] = normals[ base + j + 1 ] = n.y; + normals[ j + 2 ] = normals[ base + j + 2 ] = n.z; + + } + + } + + } + + LatheBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + LatheBufferGeometry.prototype.constructor = LatheBufferGeometry; + + /** + * @author jonobr1 / http://jonobr1.com + * @author Mugen87 / https://github.com/Mugen87 + */ + + // ShapeGeometry + + function ShapeGeometry( shapes, curveSegments ) { + + Geometry.call( this ); + + this.type = 'ShapeGeometry'; + + if ( typeof curveSegments === 'object' ) { + + console.warn( 'THREE.ShapeGeometry: Options parameter has been removed.' ); + + curveSegments = curveSegments.curveSegments; + + } + + this.parameters = { + shapes: shapes, + curveSegments: curveSegments + }; + + this.fromBufferGeometry( new ShapeBufferGeometry( shapes, curveSegments ) ); + this.mergeVertices(); + + } + + ShapeGeometry.prototype = Object.create( Geometry.prototype ); + ShapeGeometry.prototype.constructor = ShapeGeometry; + + ShapeGeometry.prototype.toJSON = function () { + + var data = Geometry.prototype.toJSON.call( this ); + + var shapes = this.parameters.shapes; + + return toJSON( shapes, data ); + + }; + + // ShapeBufferGeometry + + function ShapeBufferGeometry( shapes, curveSegments ) { + + BufferGeometry.call( this ); + + this.type = 'ShapeBufferGeometry'; + + this.parameters = { + shapes: shapes, + curveSegments: curveSegments + }; + + curveSegments = curveSegments || 12; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var groupStart = 0; + var groupCount = 0; + + // allow single and array values for "shapes" parameter + + if ( Array.isArray( shapes ) === false ) { + + addShape( shapes ); + + } else { + + for ( var i = 0; i < shapes.length; i ++ ) { + + addShape( shapes[ i ] ); + + this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support + + groupStart += groupCount; + groupCount = 0; + + } + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + + // helper functions + + function addShape( shape ) { + + var i, l, shapeHole; + + var indexOffset = vertices.length / 3; + var points = shape.extractPoints( curveSegments ); + + var shapeVertices = points.shape; + var shapeHoles = points.holes; + + // check direction of vertices + + if ( ShapeUtils.isClockWise( shapeVertices ) === false ) { + + shapeVertices = shapeVertices.reverse(); + + // also check if holes are in the opposite direction + + for ( i = 0, l = shapeHoles.length; i < l; i ++ ) { + + shapeHole = shapeHoles[ i ]; + + if ( ShapeUtils.isClockWise( shapeHole ) === true ) { + + shapeHoles[ i ] = shapeHole.reverse(); + + } + + } + + } + + var faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles ); + + // join vertices of inner and outer paths to a single array + + for ( i = 0, l = shapeHoles.length; i < l; i ++ ) { + + shapeHole = shapeHoles[ i ]; + shapeVertices = shapeVertices.concat( shapeHole ); + + } + + // vertices, normals, uvs + + for ( i = 0, l = shapeVertices.length; i < l; i ++ ) { + + var vertex = shapeVertices[ i ]; + + vertices.push( vertex.x, vertex.y, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( vertex.x, vertex.y ); // world uvs + + } + + // incides + + for ( i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + var a = face[ 0 ] + indexOffset; + var b = face[ 1 ] + indexOffset; + var c = face[ 2 ] + indexOffset; + + indices.push( a, b, c ); + groupCount += 3; + + } + + } + + } + + ShapeBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + ShapeBufferGeometry.prototype.constructor = ShapeBufferGeometry; + + ShapeBufferGeometry.prototype.toJSON = function () { + + var data = BufferGeometry.prototype.toJSON.call( this ); + + var shapes = this.parameters.shapes; + + return toJSON( shapes, data ); + + }; + + // + + function toJSON( shapes, data ) { + + data.shapes = []; + + if ( Array.isArray( shapes ) ) { + + for ( var i = 0, l = shapes.length; i < l; i ++ ) { + + var shape = shapes[ i ]; + + data.shapes.push( shape.uuid ); + + } + + } else { + + data.shapes.push( shapes.uuid ); + + } + + return data; + + } + + /** + * @author WestLangley / http://github.com/WestLangley + * @author Mugen87 / https://github.com/Mugen87 + */ + + function EdgesGeometry( geometry, thresholdAngle ) { + + BufferGeometry.call( this ); + + this.type = 'EdgesGeometry'; + + this.parameters = { + thresholdAngle: thresholdAngle + }; + + thresholdAngle = ( thresholdAngle !== undefined ) ? thresholdAngle : 1; + + // buffer + + var vertices = []; + + // helper variables + + var thresholdDot = Math.cos( _Math.DEG2RAD * thresholdAngle ); + var edge = [ 0, 0 ], edges = {}, edge1, edge2; + var key, keys = [ 'a', 'b', 'c' ]; + + // prepare source geometry + + var geometry2; + + if ( geometry.isBufferGeometry ) { + + geometry2 = new Geometry(); + geometry2.fromBufferGeometry( geometry ); + + } else { + + geometry2 = geometry.clone(); + + } + + geometry2.mergeVertices(); + geometry2.computeFaceNormals(); + + var sourceVertices = geometry2.vertices; + var faces = geometry2.faces; + + // now create a data structure where each entry represents an edge with its adjoining faces + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0; j < 3; j ++ ) { + + edge1 = face[ keys[ j ] ]; + edge2 = face[ keys[ ( j + 1 ) % 3 ] ]; + edge[ 0 ] = Math.min( edge1, edge2 ); + edge[ 1 ] = Math.max( edge1, edge2 ); + + key = edge[ 0 ] + ',' + edge[ 1 ]; + + if ( edges[ key ] === undefined ) { + + edges[ key ] = { index1: edge[ 0 ], index2: edge[ 1 ], face1: i, face2: undefined }; + + } else { + + edges[ key ].face2 = i; + + } + + } + + } + + // generate vertices + + for ( key in edges ) { + + var e = edges[ key ]; + + // an edge is only rendered if the angle (in degrees) between the face normals of the adjoining faces exceeds this value. default = 1 degree. + + if ( e.face2 === undefined || faces[ e.face1 ].normal.dot( faces[ e.face2 ].normal ) <= thresholdDot ) { + + var vertex = sourceVertices[ e.index1 ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + vertex = sourceVertices[ e.index2 ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + } + + // build geometry + + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + + EdgesGeometry.prototype = Object.create( BufferGeometry.prototype ); + EdgesGeometry.prototype.constructor = EdgesGeometry; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + // CylinderGeometry + + function CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + + Geometry.call( this ); + + this.type = 'CylinderGeometry'; + + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + this.fromBufferGeometry( new CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) ); + this.mergeVertices(); + + } + + CylinderGeometry.prototype = Object.create( Geometry.prototype ); + CylinderGeometry.prototype.constructor = CylinderGeometry; + + // CylinderBufferGeometry + + function CylinderBufferGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + + BufferGeometry.call( this ); + + this.type = 'CylinderBufferGeometry'; + + this.parameters = { + radiusTop: radiusTop, + radiusBottom: radiusBottom, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + var scope = this; + + radiusTop = radiusTop !== undefined ? radiusTop : 1; + radiusBottom = radiusBottom !== undefined ? radiusBottom : 1; + height = height || 1; + + radialSegments = Math.floor( radialSegments ) || 8; + heightSegments = Math.floor( heightSegments ) || 1; + + openEnded = openEnded !== undefined ? openEnded : false; + thetaStart = thetaStart !== undefined ? thetaStart : 0.0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var index = 0; + var indexArray = []; + var halfHeight = height / 2; + var groupStart = 0; + + // generate geometry + + generateTorso(); + + if ( openEnded === false ) { + + if ( radiusTop > 0 ) generateCap( true ); + if ( radiusBottom > 0 ) generateCap( false ); + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function generateTorso() { + + var x, y; + var normal = new Vector3(); + var vertex = new Vector3(); + + var groupCount = 0; + + // this will be used to calculate the normal + var slope = ( radiusBottom - radiusTop ) / height; + + // generate vertices, normals and uvs + + for ( y = 0; y <= heightSegments; y ++ ) { + + var indexRow = []; + + var v = y / heightSegments; + + // calculate the radius of the current row + + var radius = v * ( radiusBottom - radiusTop ) + radiusTop; + + for ( x = 0; x <= radialSegments; x ++ ) { + + var u = x / radialSegments; + + var theta = u * thetaLength + thetaStart; + + var sinTheta = Math.sin( theta ); + var cosTheta = Math.cos( theta ); + + // vertex + + vertex.x = radius * sinTheta; + vertex.y = - v * height + halfHeight; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normal.set( sinTheta, slope, cosTheta ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, 1 - v ); + + // save index of vertex in respective row + + indexRow.push( index ++ ); + + } + + // now save vertices of the row in our index array + + indexArray.push( indexRow ); + + } + + // generate indices + + for ( x = 0; x < radialSegments; x ++ ) { + + for ( y = 0; y < heightSegments; y ++ ) { + + // we use the index array to access the correct indices + + var a = indexArray[ y ][ x ]; + var b = indexArray[ y + 1 ][ x ]; + var c = indexArray[ y + 1 ][ x + 1 ]; + var d = indexArray[ y ][ x + 1 ]; + + // faces + + indices.push( a, b, d ); + indices.push( b, c, d ); + + // update group counter + + groupCount += 6; + + } + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, 0 ); + + // calculate new start value for groups + + groupStart += groupCount; + + } + + function generateCap( top ) { + + var x, centerIndexStart, centerIndexEnd; + + var uv = new Vector2(); + var vertex = new Vector3(); + + var groupCount = 0; + + var radius = ( top === true ) ? radiusTop : radiusBottom; + var sign = ( top === true ) ? 1 : - 1; + + // save the index of the first center vertex + centerIndexStart = index; + + // first we generate the center vertex data of the cap. + // because the geometry needs one set of uvs per face, + // we must generate a center vertex per face/segment + + for ( x = 1; x <= radialSegments; x ++ ) { + + // vertex + + vertices.push( 0, halfHeight * sign, 0 ); + + // normal + + normals.push( 0, sign, 0 ); + + // uv + + uvs.push( 0.5, 0.5 ); + + // increase index + + index ++; + + } + + // save the index of the last center vertex + + centerIndexEnd = index; + + // now we generate the surrounding vertices, normals and uvs + + for ( x = 0; x <= radialSegments; x ++ ) { + + var u = x / radialSegments; + var theta = u * thetaLength + thetaStart; + + var cosTheta = Math.cos( theta ); + var sinTheta = Math.sin( theta ); + + // vertex + + vertex.x = radius * sinTheta; + vertex.y = halfHeight * sign; + vertex.z = radius * cosTheta; + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, sign, 0 ); + + // uv + + uv.x = ( cosTheta * 0.5 ) + 0.5; + uv.y = ( sinTheta * 0.5 * sign ) + 0.5; + uvs.push( uv.x, uv.y ); + + // increase index + + index ++; + + } + + // generate indices + + for ( x = 0; x < radialSegments; x ++ ) { + + var c = centerIndexStart + x; + var i = centerIndexEnd + x; + + if ( top === true ) { + + // face top + + indices.push( i, i + 1, c ); + + } else { + + // face bottom + + indices.push( i + 1, i, c ); + + } + + groupCount += 3; + + } + + // add a group to the geometry. this will ensure multi material support + + scope.addGroup( groupStart, groupCount, top === true ? 1 : 2 ); + + // calculate new start value for groups + + groupStart += groupCount; + + } + + } + + CylinderBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + CylinderBufferGeometry.prototype.constructor = CylinderBufferGeometry; + + /** + * @author abelnation / http://github.com/abelnation + */ + + // ConeGeometry + + function ConeGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + + CylinderGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + + this.type = 'ConeGeometry'; + + this.parameters = { + radius: radius, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + } + + ConeGeometry.prototype = Object.create( CylinderGeometry.prototype ); + ConeGeometry.prototype.constructor = ConeGeometry; + + // ConeBufferGeometry + + function ConeBufferGeometry( radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ) { + + CylinderBufferGeometry.call( this, 0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength ); + + this.type = 'ConeBufferGeometry'; + + this.parameters = { + radius: radius, + height: height, + radialSegments: radialSegments, + heightSegments: heightSegments, + openEnded: openEnded, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + } + + ConeBufferGeometry.prototype = Object.create( CylinderBufferGeometry.prototype ); + ConeBufferGeometry.prototype.constructor = ConeBufferGeometry; + + /** + * @author benaadams / https://twitter.com/ben_a_adams + * @author Mugen87 / https://github.com/Mugen87 + * @author hughes + */ + + // CircleGeometry + + function CircleGeometry( radius, segments, thetaStart, thetaLength ) { + + Geometry.call( this ); + + this.type = 'CircleGeometry'; + + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + this.fromBufferGeometry( new CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) ); + this.mergeVertices(); + + } + + CircleGeometry.prototype = Object.create( Geometry.prototype ); + CircleGeometry.prototype.constructor = CircleGeometry; + + // CircleBufferGeometry + + function CircleBufferGeometry( radius, segments, thetaStart, thetaLength ) { + + BufferGeometry.call( this ); + + this.type = 'CircleBufferGeometry'; + + this.parameters = { + radius: radius, + segments: segments, + thetaStart: thetaStart, + thetaLength: thetaLength + }; + + radius = radius || 1; + segments = segments !== undefined ? Math.max( 3, segments ) : 8; + + thetaStart = thetaStart !== undefined ? thetaStart : 0; + thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2; + + // buffers + + var indices = []; + var vertices = []; + var normals = []; + var uvs = []; + + // helper variables + + var i, s; + var vertex = new Vector3(); + var uv = new Vector2(); + + // center point + + vertices.push( 0, 0, 0 ); + normals.push( 0, 0, 1 ); + uvs.push( 0.5, 0.5 ); + + for ( s = 0, i = 3; s <= segments; s ++, i += 3 ) { + + var segment = thetaStart + s / segments * thetaLength; + + // vertex + + vertex.x = radius * Math.cos( segment ); + vertex.y = radius * Math.sin( segment ); + + vertices.push( vertex.x, vertex.y, vertex.z ); + + // normal + + normals.push( 0, 0, 1 ); + + // uvs + + uv.x = ( vertices[ i ] / radius + 1 ) / 2; + uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2; + + uvs.push( uv.x, uv.y ); + + } + + // indices + + for ( i = 1; i <= segments; i ++ ) { + + indices.push( i, i + 1, 0 ); + + } + + // build geometry + + this.setIndex( indices ); + this.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + CircleBufferGeometry.prototype = Object.create( BufferGeometry.prototype ); + CircleBufferGeometry.prototype.constructor = CircleBufferGeometry; + + + + var Geometries = Object.freeze({ + WireframeGeometry: WireframeGeometry, + ParametricGeometry: ParametricGeometry, + ParametricBufferGeometry: ParametricBufferGeometry, + TetrahedronGeometry: TetrahedronGeometry, + TetrahedronBufferGeometry: TetrahedronBufferGeometry, + OctahedronGeometry: OctahedronGeometry, + OctahedronBufferGeometry: OctahedronBufferGeometry, + IcosahedronGeometry: IcosahedronGeometry, + IcosahedronBufferGeometry: IcosahedronBufferGeometry, + DodecahedronGeometry: DodecahedronGeometry, + DodecahedronBufferGeometry: DodecahedronBufferGeometry, + PolyhedronGeometry: PolyhedronGeometry, + PolyhedronBufferGeometry: PolyhedronBufferGeometry, + TubeGeometry: TubeGeometry, + TubeBufferGeometry: TubeBufferGeometry, + TorusKnotGeometry: TorusKnotGeometry, + TorusKnotBufferGeometry: TorusKnotBufferGeometry, + TorusGeometry: TorusGeometry, + TorusBufferGeometry: TorusBufferGeometry, + TextGeometry: TextGeometry, + TextBufferGeometry: TextBufferGeometry, + SphereGeometry: SphereGeometry, + SphereBufferGeometry: SphereBufferGeometry, + RingGeometry: RingGeometry, + RingBufferGeometry: RingBufferGeometry, + PlaneGeometry: PlaneGeometry, + PlaneBufferGeometry: PlaneBufferGeometry, + LatheGeometry: LatheGeometry, + LatheBufferGeometry: LatheBufferGeometry, + ShapeGeometry: ShapeGeometry, + ShapeBufferGeometry: ShapeBufferGeometry, + ExtrudeGeometry: ExtrudeGeometry, + ExtrudeBufferGeometry: ExtrudeBufferGeometry, + EdgesGeometry: EdgesGeometry, + ConeGeometry: ConeGeometry, + ConeBufferGeometry: ConeBufferGeometry, + CylinderGeometry: CylinderGeometry, + CylinderBufferGeometry: CylinderBufferGeometry, + CircleGeometry: CircleGeometry, + CircleBufferGeometry: CircleBufferGeometry, + BoxGeometry: BoxGeometry, + BoxBufferGeometry: BoxBufferGeometry + }); + + /** + * @author mrdoob / http://mrdoob.com/ + * + * parameters = { + * color: , + * opacity: + * } + */ + + function ShadowMaterial( parameters ) { + + Material.call( this ); + + this.type = 'ShadowMaterial'; + + this.color = new Color( 0x000000 ); + this.opacity = 1.0; + + this.lights = true; + this.transparent = true; + + this.setValues( parameters ); + + } + + ShadowMaterial.prototype = Object.create( Material.prototype ); + ShadowMaterial.prototype.constructor = ShadowMaterial; + + ShadowMaterial.prototype.isShadowMaterial = true; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function RawShaderMaterial( parameters ) { + + ShaderMaterial.call( this, parameters ); + + this.type = 'RawShaderMaterial'; + + } + + RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype ); + RawShaderMaterial.prototype.constructor = RawShaderMaterial; + + RawShaderMaterial.prototype.isRawShaderMaterial = true; + + /** + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * color: , + * roughness: , + * metalness: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * lightMapIntensity: + * + * aoMap: new THREE.Texture( ), + * aoMapIntensity: + * + * emissive: , + * emissiveIntensity: + * emissiveMap: new THREE.Texture( ), + * + * bumpMap: new THREE.Texture( ), + * bumpScale: , + * + * normalMap: new THREE.Texture( ), + * normalScale: , + * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: , + * + * roughnessMap: new THREE.Texture( ), + * + * metalnessMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ), + * envMapIntensity: + * + * refractionRatio: , + * + * wireframe: , + * wireframeLinewidth: , + * + * skinning: , + * morphTargets: , + * morphNormals: + * } + */ + + function MeshStandardMaterial( parameters ) { + + Material.call( this ); + + this.defines = { 'STANDARD': '' }; + + this.type = 'MeshStandardMaterial'; + + this.color = new Color( 0xffffff ); // diffuse + this.roughness = 0.5; + this.metalness = 0.5; + + this.map = null; + + this.lightMap = null; + this.lightMapIntensity = 1.0; + + this.aoMap = null; + this.aoMapIntensity = 1.0; + + this.emissive = new Color( 0x000000 ); + this.emissiveIntensity = 1.0; + this.emissiveMap = null; + + this.bumpMap = null; + this.bumpScale = 1; + + this.normalMap = null; + this.normalScale = new Vector2( 1, 1 ); + + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; + + this.roughnessMap = null; + + this.metalnessMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.envMapIntensity = 1.0; + + this.refractionRatio = 0.98; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + + } + + MeshStandardMaterial.prototype = Object.create( Material.prototype ); + MeshStandardMaterial.prototype.constructor = MeshStandardMaterial; + + MeshStandardMaterial.prototype.isMeshStandardMaterial = true; + + MeshStandardMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.defines = { 'STANDARD': '' }; + + this.color.copy( source.color ); + this.roughness = source.roughness; + this.metalness = source.metalness; + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.roughnessMap = source.roughnessMap; + + this.metalnessMap = source.metalnessMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.envMapIntensity = source.envMapIntensity; + + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; + + return this; + + }; + + /** + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * reflectivity: + * } + */ + + function MeshPhysicalMaterial( parameters ) { + + MeshStandardMaterial.call( this ); + + this.defines = { 'PHYSICAL': '' }; + + this.type = 'MeshPhysicalMaterial'; + + this.reflectivity = 0.5; // maps to F0 = 0.04 + + this.clearCoat = 0.0; + this.clearCoatRoughness = 0.0; + + this.setValues( parameters ); + + } + + MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype ); + MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial; + + MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true; + + MeshPhysicalMaterial.prototype.copy = function ( source ) { + + MeshStandardMaterial.prototype.copy.call( this, source ); + + this.defines = { 'PHYSICAL': '' }; + + this.reflectivity = source.reflectivity; + + this.clearCoat = source.clearCoat; + this.clearCoatRoughness = source.clearCoatRoughness; + + return this; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * specular: , + * shininess: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * lightMapIntensity: + * + * aoMap: new THREE.Texture( ), + * aoMapIntensity: + * + * emissive: , + * emissiveIntensity: + * emissiveMap: new THREE.Texture( ), + * + * bumpMap: new THREE.Texture( ), + * bumpScale: , + * + * normalMap: new THREE.Texture( ), + * normalScale: , + * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: , + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * wireframe: , + * wireframeLinewidth: , + * + * skinning: , + * morphTargets: , + * morphNormals: + * } + */ + + function MeshPhongMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshPhongMaterial'; + + this.color = new Color( 0xffffff ); // diffuse + this.specular = new Color( 0x111111 ); + this.shininess = 30; + + this.map = null; + + this.lightMap = null; + this.lightMapIntensity = 1.0; + + this.aoMap = null; + this.aoMapIntensity = 1.0; + + this.emissive = new Color( 0x000000 ); + this.emissiveIntensity = 1.0; + this.emissiveMap = null; + + this.bumpMap = null; + this.bumpScale = 1; + + this.normalMap = null; + this.normalScale = new Vector2( 1, 1 ); + + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + + } + + MeshPhongMaterial.prototype = Object.create( Material.prototype ); + MeshPhongMaterial.prototype.constructor = MeshPhongMaterial; + + MeshPhongMaterial.prototype.isMeshPhongMaterial = true; + + MeshPhongMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + this.specular.copy( source.specular ); + this.shininess = source.shininess; + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; + + return this; + + }; + + /** + * @author takahirox / http://github.com/takahirox + * + * parameters = { + * gradientMap: new THREE.Texture( ) + * } + */ + + function MeshToonMaterial( parameters ) { + + MeshPhongMaterial.call( this ); + + this.defines = { 'TOON': '' }; + + this.type = 'MeshToonMaterial'; + + this.gradientMap = null; + + this.setValues( parameters ); + + } + + MeshToonMaterial.prototype = Object.create( MeshPhongMaterial.prototype ); + MeshToonMaterial.prototype.constructor = MeshToonMaterial; + + MeshToonMaterial.prototype.isMeshToonMaterial = true; + + MeshToonMaterial.prototype.copy = function ( source ) { + + MeshPhongMaterial.prototype.copy.call( this, source ); + + this.gradientMap = source.gradientMap; + + return this; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + * + * parameters = { + * opacity: , + * + * bumpMap: new THREE.Texture( ), + * bumpScale: , + * + * normalMap: new THREE.Texture( ), + * normalScale: , + * + * displacementMap: new THREE.Texture( ), + * displacementScale: , + * displacementBias: , + * + * wireframe: , + * wireframeLinewidth: + * + * skinning: , + * morphTargets: , + * morphNormals: + * } + */ + + function MeshNormalMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshNormalMaterial'; + + this.bumpMap = null; + this.bumpScale = 1; + + this.normalMap = null; + this.normalScale = new Vector2( 1, 1 ); + + this.displacementMap = null; + this.displacementScale = 1; + this.displacementBias = 0; + + this.wireframe = false; + this.wireframeLinewidth = 1; + + this.fog = false; + this.lights = false; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + + } + + MeshNormalMaterial.prototype = Object.create( Material.prototype ); + MeshNormalMaterial.prototype.constructor = MeshNormalMaterial; + + MeshNormalMaterial.prototype.isMeshNormalMaterial = true; + + MeshNormalMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.bumpMap = source.bumpMap; + this.bumpScale = source.bumpScale; + + this.normalMap = source.normalMap; + this.normalScale.copy( source.normalScale ); + + this.displacementMap = source.displacementMap; + this.displacementScale = source.displacementScale; + this.displacementBias = source.displacementBias; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; + + return this; + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * map: new THREE.Texture( ), + * + * lightMap: new THREE.Texture( ), + * lightMapIntensity: + * + * aoMap: new THREE.Texture( ), + * aoMapIntensity: + * + * emissive: , + * emissiveIntensity: + * emissiveMap: new THREE.Texture( ), + * + * specularMap: new THREE.Texture( ), + * + * alphaMap: new THREE.Texture( ), + * + * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ), + * combine: THREE.Multiply, + * reflectivity: , + * refractionRatio: , + * + * wireframe: , + * wireframeLinewidth: , + * + * skinning: , + * morphTargets: , + * morphNormals: + * } + */ + + function MeshLambertMaterial( parameters ) { + + Material.call( this ); + + this.type = 'MeshLambertMaterial'; + + this.color = new Color( 0xffffff ); // diffuse + + this.map = null; + + this.lightMap = null; + this.lightMapIntensity = 1.0; + + this.aoMap = null; + this.aoMapIntensity = 1.0; + + this.emissive = new Color( 0x000000 ); + this.emissiveIntensity = 1.0; + this.emissiveMap = null; + + this.specularMap = null; + + this.alphaMap = null; + + this.envMap = null; + this.combine = MultiplyOperation; + this.reflectivity = 1; + this.refractionRatio = 0.98; + + this.wireframe = false; + this.wireframeLinewidth = 1; + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.skinning = false; + this.morphTargets = false; + this.morphNormals = false; + + this.setValues( parameters ); + + } + + MeshLambertMaterial.prototype = Object.create( Material.prototype ); + MeshLambertMaterial.prototype.constructor = MeshLambertMaterial; + + MeshLambertMaterial.prototype.isMeshLambertMaterial = true; + + MeshLambertMaterial.prototype.copy = function ( source ) { + + Material.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + + this.map = source.map; + + this.lightMap = source.lightMap; + this.lightMapIntensity = source.lightMapIntensity; + + this.aoMap = source.aoMap; + this.aoMapIntensity = source.aoMapIntensity; + + this.emissive.copy( source.emissive ); + this.emissiveMap = source.emissiveMap; + this.emissiveIntensity = source.emissiveIntensity; + + this.specularMap = source.specularMap; + + this.alphaMap = source.alphaMap; + + this.envMap = source.envMap; + this.combine = source.combine; + this.reflectivity = source.reflectivity; + this.refractionRatio = source.refractionRatio; + + this.wireframe = source.wireframe; + this.wireframeLinewidth = source.wireframeLinewidth; + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.skinning = source.skinning; + this.morphTargets = source.morphTargets; + this.morphNormals = source.morphNormals; + + return this; + + }; + + /** + * @author alteredq / http://alteredqualia.com/ + * + * parameters = { + * color: , + * opacity: , + * + * linewidth: , + * + * scale: , + * dashSize: , + * gapSize: + * } + */ + + function LineDashedMaterial( parameters ) { + + LineBasicMaterial.call( this ); + + this.type = 'LineDashedMaterial'; + + this.scale = 1; + this.dashSize = 3; + this.gapSize = 1; + + this.setValues( parameters ); + + } + + LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype ); + LineDashedMaterial.prototype.constructor = LineDashedMaterial; + + LineDashedMaterial.prototype.isLineDashedMaterial = true; + + LineDashedMaterial.prototype.copy = function ( source ) { + + LineBasicMaterial.prototype.copy.call( this, source ); + + this.scale = source.scale; + this.dashSize = source.dashSize; + this.gapSize = source.gapSize; + + return this; + + }; + + + + var Materials = Object.freeze({ + ShadowMaterial: ShadowMaterial, + SpriteMaterial: SpriteMaterial, + RawShaderMaterial: RawShaderMaterial, + ShaderMaterial: ShaderMaterial, + PointsMaterial: PointsMaterial, + MeshPhysicalMaterial: MeshPhysicalMaterial, + MeshStandardMaterial: MeshStandardMaterial, + MeshPhongMaterial: MeshPhongMaterial, + MeshToonMaterial: MeshToonMaterial, + MeshNormalMaterial: MeshNormalMaterial, + MeshLambertMaterial: MeshLambertMaterial, + MeshDepthMaterial: MeshDepthMaterial, + MeshDistanceMaterial: MeshDistanceMaterial, + MeshBasicMaterial: MeshBasicMaterial, + LineDashedMaterial: LineDashedMaterial, + LineBasicMaterial: LineBasicMaterial, + Material: Material + }); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + var Cache = { + + enabled: false, + + files: {}, + + add: function ( key, file ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Adding key:', key ); + + this.files[ key ] = file; + + }, + + get: function ( key ) { + + if ( this.enabled === false ) return; + + // console.log( 'THREE.Cache', 'Checking key:', key ); + + return this.files[ key ]; + + }, + + remove: function ( key ) { + + delete this.files[ key ]; + + }, + + clear: function () { + + this.files = {}; + + } + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function LoadingManager( onLoad, onProgress, onError ) { + + var scope = this; + + var isLoading = false; + var itemsLoaded = 0; + var itemsTotal = 0; + var urlModifier = undefined; + + this.onStart = undefined; + this.onLoad = onLoad; + this.onProgress = onProgress; + this.onError = onError; + + this.itemStart = function ( url ) { + + itemsTotal ++; + + if ( isLoading === false ) { + + if ( scope.onStart !== undefined ) { + + scope.onStart( url, itemsLoaded, itemsTotal ); + + } + + } + + isLoading = true; + + }; + + this.itemEnd = function ( url ) { + + itemsLoaded ++; + + if ( scope.onProgress !== undefined ) { + + scope.onProgress( url, itemsLoaded, itemsTotal ); + + } + + if ( itemsLoaded === itemsTotal ) { + + isLoading = false; + + if ( scope.onLoad !== undefined ) { + + scope.onLoad(); + + } + + } + + }; + + this.itemError = function ( url ) { + + if ( scope.onError !== undefined ) { + + scope.onError( url ); + + } + + }; + + this.resolveURL = function ( url ) { + + if ( urlModifier ) { + + return urlModifier( url ); + + } + + return url; + + }; + + this.setURLModifier = function ( transform ) { + + urlModifier = transform; + return this; + + }; + + } + + var DefaultLoadingManager = new LoadingManager(); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + var loading = {}; + + function FileLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( FileLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + if ( url === undefined ) url = ''; + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + var scope = this; + + var cached = Cache.get( url ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + // Check if request is duplicate + + if ( loading[ url ] !== undefined ) { + + loading[ url ].push( { + + onLoad: onLoad, + onProgress: onProgress, + onError: onError + + } ); + + return; + + } + + // Check for data: URI + var dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/; + var dataUriRegexResult = url.match( dataUriRegex ); + + // Safari can not handle Data URIs through XMLHttpRequest so process manually + if ( dataUriRegexResult ) { + + var mimeType = dataUriRegexResult[ 1 ]; + var isBase64 = !! dataUriRegexResult[ 2 ]; + var data = dataUriRegexResult[ 3 ]; + + data = window.decodeURIComponent( data ); + + if ( isBase64 ) data = window.atob( data ); + + try { + + var response; + var responseType = ( this.responseType || '' ).toLowerCase(); + + switch ( responseType ) { + + case 'arraybuffer': + case 'blob': + + var view = new Uint8Array( data.length ); + + for ( var i = 0; i < data.length; i ++ ) { + + view[ i ] = data.charCodeAt( i ); + + } + + if ( responseType === 'blob' ) { + + response = new Blob( [ view.buffer ], { type: mimeType } ); + + } else { + + response = view.buffer; + + } + + break; + + case 'document': + + var parser = new DOMParser(); + response = parser.parseFromString( data, mimeType ); + + break; + + case 'json': + + response = JSON.parse( data ); + + break; + + default: // 'text' or other + + response = data; + + break; + + } + + // Wait for next browser tick like standard XMLHttpRequest event dispatching does + window.setTimeout( function () { + + if ( onLoad ) onLoad( response ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + } catch ( error ) { + + // Wait for next browser tick like standard XMLHttpRequest event dispatching does + window.setTimeout( function () { + + if ( onError ) onError( error ); + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + }, 0 ); + + } + + } else { + + // Initialise array for duplicate requests + + loading[ url ] = []; + + loading[ url ].push( { + + onLoad: onLoad, + onProgress: onProgress, + onError: onError + + } ); + + var request = new XMLHttpRequest(); + + request.open( 'GET', url, true ); + + request.addEventListener( 'load', function ( event ) { + + var response = this.response; + + Cache.add( url, response ); + + var callbacks = loading[ url ]; + + delete loading[ url ]; + + if ( this.status === 200 ) { + + for ( var i = 0, il = callbacks.length; i < il; i ++ ) { + + var callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( response ); + + } + + scope.manager.itemEnd( url ); + + } else if ( this.status === 0 ) { + + // Some browsers return HTTP Status 0 when using non-http protocol + // e.g. 'file://' or 'data://'. Handle as success. + + console.warn( 'THREE.FileLoader: HTTP Status 0 received.' ); + + for ( var i = 0, il = callbacks.length; i < il; i ++ ) { + + var callback = callbacks[ i ]; + if ( callback.onLoad ) callback.onLoad( response ); + + } + + scope.manager.itemEnd( url ); + + } else { + + for ( var i = 0, il = callbacks.length; i < il; i ++ ) { + + var callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); + + } + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + } + + }, false ); + + request.addEventListener( 'progress', function ( event ) { + + var callbacks = loading[ url ]; + + for ( var i = 0, il = callbacks.length; i < il; i ++ ) { + + var callback = callbacks[ i ]; + if ( callback.onProgress ) callback.onProgress( event ); + + } + + }, false ); + + request.addEventListener( 'error', function ( event ) { + + var callbacks = loading[ url ]; + + delete loading[ url ]; + + for ( var i = 0, il = callbacks.length; i < il; i ++ ) { + + var callback = callbacks[ i ]; + if ( callback.onError ) callback.onError( event ); + + } + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + }, false ); + + if ( this.responseType !== undefined ) request.responseType = this.responseType; + if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials; + + if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' ); + + for ( var header in this.requestHeader ) { + + request.setRequestHeader( header, this.requestHeader[ header ] ); + + } + + request.send( null ); + + } + + scope.manager.itemStart( url ); + + return request; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + }, + + setResponseType: function ( value ) { + + this.responseType = value; + return this; + + }, + + setWithCredentials: function ( value ) { + + this.withCredentials = value; + return this; + + }, + + setMimeType: function ( value ) { + + this.mimeType = value; + return this; + + }, + + setRequestHeader: function ( value ) { + + this.requestHeader = value; + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * + * Abstract Base class to block based textures loader (dds, pvr, ...) + */ + + function CompressedTextureLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + // override in sub classes + this._parser = null; + + } + + Object.assign( CompressedTextureLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var images = []; + + var texture = new CompressedTexture(); + texture.image = images; + + var loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + + function loadTexture( i ) { + + loader.load( url[ i ], function ( buffer ) { + + var texDatas = scope._parser( buffer, true ); + + images[ i ] = { + width: texDatas.width, + height: texDatas.height, + format: texDatas.format, + mipmaps: texDatas.mipmaps + }; + + loaded += 1; + + if ( loaded === 6 ) { + + if ( texDatas.mipmapCount === 1 ) + texture.minFilter = LinearFilter; + + texture.format = texDatas.format; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, onProgress, onError ); + + } + + if ( Array.isArray( url ) ) { + + var loaded = 0; + + for ( var i = 0, il = url.length; i < il; ++ i ) { + + loadTexture( i ); + + } + + } else { + + // compressed cubemap texture stored in a single DDS file + + loader.load( url, function ( buffer ) { + + var texDatas = scope._parser( buffer, true ); + + if ( texDatas.isCubemap ) { + + var faces = texDatas.mipmaps.length / texDatas.mipmapCount; + + for ( var f = 0; f < faces; f ++ ) { + + images[ f ] = { mipmaps: [] }; + + for ( var i = 0; i < texDatas.mipmapCount; i ++ ) { + + images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] ); + images[ f ].format = texDatas.format; + images[ f ].width = texDatas.width; + images[ f ].height = texDatas.height; + + } + + } + + } else { + + texture.image.width = texDatas.width; + texture.image.height = texDatas.height; + texture.mipmaps = texDatas.mipmaps; + + } + + if ( texDatas.mipmapCount === 1 ) { + + texture.minFilter = LinearFilter; + + } + + texture.format = texDatas.format; + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + }, onProgress, onError ); + + } + + return texture; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + } ); + + /** + * @author Nikos M. / https://github.com/foo123/ + * + * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...) + */ + + function DataTextureLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + // override in sub classes + this._parser = null; + + } + + Object.assign( DataTextureLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var texture = new DataTexture(); + + var loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + + loader.load( url, function ( buffer ) { + + var texData = scope._parser( buffer ); + + if ( ! texData ) return; + + if ( undefined !== texData.image ) { + + texture.image = texData.image; + + } else if ( undefined !== texData.data ) { + + texture.image.width = texData.width; + texture.image.height = texData.height; + texture.image.data = texData.data; + + } + + texture.wrapS = undefined !== texData.wrapS ? texData.wrapS : ClampToEdgeWrapping; + texture.wrapT = undefined !== texData.wrapT ? texData.wrapT : ClampToEdgeWrapping; + + texture.magFilter = undefined !== texData.magFilter ? texData.magFilter : LinearFilter; + texture.minFilter = undefined !== texData.minFilter ? texData.minFilter : LinearMipMapLinearFilter; + + texture.anisotropy = undefined !== texData.anisotropy ? texData.anisotropy : 1; + + if ( undefined !== texData.format ) { + + texture.format = texData.format; + + } + if ( undefined !== texData.type ) { + + texture.type = texData.type; + + } + + if ( undefined !== texData.mipmaps ) { + + texture.mipmaps = texData.mipmaps; + + } + + if ( 1 === texData.mipmapCount ) { + + texture.minFilter = LinearFilter; + + } + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture, texData ); + + }, onProgress, onError ); + + + return texture; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function ImageLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( ImageLoader.prototype, { + + crossOrigin: 'Anonymous', + + load: function ( url, onLoad, onProgress, onError ) { + + if ( url === undefined ) url = ''; + + if ( this.path !== undefined ) url = this.path + url; + + url = this.manager.resolveURL( url ); + + var scope = this; + + var cached = Cache.get( url ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + var image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' ); + + image.addEventListener( 'load', function () { + + Cache.add( url, this ); + + if ( onLoad ) onLoad( this ); + + scope.manager.itemEnd( url ); + + }, false ); + + /* + image.addEventListener( 'progress', function ( event ) { + + if ( onProgress ) onProgress( event ); + + }, false ); + */ + + image.addEventListener( 'error', function ( event ) { + + if ( onError ) onError( event ); + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + }, false ); + + if ( url.substr( 0, 5 ) !== 'data:' ) { + + if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin; + + } + + scope.manager.itemStart( url ); + + image.src = url; + + return image; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function CubeTextureLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( CubeTextureLoader.prototype, { + + crossOrigin: 'Anonymous', + + load: function ( urls, onLoad, onProgress, onError ) { + + var texture = new CubeTexture(); + + var loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + var loaded = 0; + + function loadTexture( i ) { + + loader.load( urls[ i ], function ( image ) { + + texture.images[ i ] = image; + + loaded ++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, undefined, onError ); + + } + + for ( var i = 0; i < urls.length; ++ i ) { + + loadTexture( i ); + + } + + return texture; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function TextureLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( TextureLoader.prototype, { + + crossOrigin: 'Anonymous', + + load: function ( url, onLoad, onProgress, onError ) { + + var texture = new Texture(); + + var loader = new ImageLoader( this.manager ); + loader.setCrossOrigin( this.crossOrigin ); + loader.setPath( this.path ); + + loader.load( url, function ( image ) { + + texture.image = image; + + // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB. + var isJPEG = url.search( /\.(jpg|jpeg)$/ ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0; + + texture.format = isJPEG ? RGBFormat : RGBAFormat; + texture.needsUpdate = true; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + return this; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + } ); + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Extensible curve object + * + * Some common of curve methods: + * .getPoint( t, optionalTarget ), .getTangent( t ) + * .getPointAt( u, optionalTarget ), .getTangentAt( u ) + * .getPoints(), .getSpacedPoints() + * .getLength() + * .updateArcLengths() + * + * This following curves inherit from THREE.Curve: + * + * -- 2D curves -- + * THREE.ArcCurve + * THREE.CubicBezierCurve + * THREE.EllipseCurve + * THREE.LineCurve + * THREE.QuadraticBezierCurve + * THREE.SplineCurve + * + * -- 3D curves -- + * THREE.CatmullRomCurve3 + * THREE.CubicBezierCurve3 + * THREE.LineCurve3 + * THREE.QuadraticBezierCurve3 + * + * A series of curves can be represented as a THREE.CurvePath. + * + **/ + + /************************************************************** + * Abstract Curve base class + **************************************************************/ + + function Curve() { + + this.type = 'Curve'; + + this.arcLengthDivisions = 200; + + } + + Object.assign( Curve.prototype, { + + // Virtual base class method to overwrite and implement in subclasses + // - t [0 .. 1] + + getPoint: function ( /* t, optionalTarget */ ) { + + console.warn( 'THREE.Curve: .getPoint() not implemented.' ); + return null; + + }, + + // Get point at relative position in curve according to arc length + // - u [0 .. 1] + + getPointAt: function ( u, optionalTarget ) { + + var t = this.getUtoTmapping( u ); + return this.getPoint( t, optionalTarget ); + + }, + + // Get sequence of points using getPoint( t ) + + getPoints: function ( divisions ) { + + if ( divisions === undefined ) divisions = 5; + + var points = []; + + for ( var d = 0; d <= divisions; d ++ ) { + + points.push( this.getPoint( d / divisions ) ); + + } + + return points; + + }, + + // Get sequence of points using getPointAt( u ) + + getSpacedPoints: function ( divisions ) { + + if ( divisions === undefined ) divisions = 5; + + var points = []; + + for ( var d = 0; d <= divisions; d ++ ) { + + points.push( this.getPointAt( d / divisions ) ); + + } + + return points; + + }, + + // Get total curve arc length + + getLength: function () { + + var lengths = this.getLengths(); + return lengths[ lengths.length - 1 ]; + + }, + + // Get list of cumulative segment lengths + + getLengths: function ( divisions ) { + + if ( divisions === undefined ) divisions = this.arcLengthDivisions; + + if ( this.cacheArcLengths && + ( this.cacheArcLengths.length === divisions + 1 ) && + ! this.needsUpdate ) { + + return this.cacheArcLengths; + + } + + this.needsUpdate = false; + + var cache = []; + var current, last = this.getPoint( 0 ); + var p, sum = 0; + + cache.push( 0 ); + + for ( p = 1; p <= divisions; p ++ ) { + + current = this.getPoint( p / divisions ); + sum += current.distanceTo( last ); + cache.push( sum ); + last = current; + + } + + this.cacheArcLengths = cache; + + return cache; // { sums: cache, sum: sum }; Sum is in the last element. + + }, + + updateArcLengths: function () { + + this.needsUpdate = true; + this.getLengths(); + + }, + + // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant + + getUtoTmapping: function ( u, distance ) { + + var arcLengths = this.getLengths(); + + var i = 0, il = arcLengths.length; + + var targetArcLength; // The targeted u distance value to get + + if ( distance ) { + + targetArcLength = distance; + + } else { + + targetArcLength = u * arcLengths[ il - 1 ]; + + } + + // binary search for the index with largest value smaller than target u distance + + var low = 0, high = il - 1, comparison; + + while ( low <= high ) { + + i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats + + comparison = arcLengths[ i ] - targetArcLength; + + if ( comparison < 0 ) { + + low = i + 1; + + } else if ( comparison > 0 ) { + + high = i - 1; + + } else { + + high = i; + break; + + // DONE + + } + + } + + i = high; + + if ( arcLengths[ i ] === targetArcLength ) { + + return i / ( il - 1 ); + + } + + // we could get finer grain at lengths, or use simple interpolation between two points + + var lengthBefore = arcLengths[ i ]; + var lengthAfter = arcLengths[ i + 1 ]; + + var segmentLength = lengthAfter - lengthBefore; + + // determine where we are between the 'before' and 'after' points + + var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength; + + // add that fractional amount to t + + var t = ( i + segmentFraction ) / ( il - 1 ); + + return t; + + }, + + // Returns a unit vector tangent at t + // In case any sub curve does not implement its tangent derivation, + // 2 points a small delta apart will be used to find its gradient + // which seems to give a reasonable approximation + + getTangent: function ( t ) { + + var delta = 0.0001; + var t1 = t - delta; + var t2 = t + delta; + + // Capping in case of danger + + if ( t1 < 0 ) t1 = 0; + if ( t2 > 1 ) t2 = 1; + + var pt1 = this.getPoint( t1 ); + var pt2 = this.getPoint( t2 ); + + var vec = pt2.clone().sub( pt1 ); + return vec.normalize(); + + }, + + getTangentAt: function ( u ) { + + var t = this.getUtoTmapping( u ); + return this.getTangent( t ); + + }, + + computeFrenetFrames: function ( segments, closed ) { + + // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf + + var normal = new Vector3(); + + var tangents = []; + var normals = []; + var binormals = []; + + var vec = new Vector3(); + var mat = new Matrix4(); + + var i, u, theta; + + // compute the tangent vectors for each segment on the curve + + for ( i = 0; i <= segments; i ++ ) { + + u = i / segments; + + tangents[ i ] = this.getTangentAt( u ); + tangents[ i ].normalize(); + + } + + // select an initial normal vector perpendicular to the first tangent vector, + // and in the direction of the minimum tangent xyz component + + normals[ 0 ] = new Vector3(); + binormals[ 0 ] = new Vector3(); + var min = Number.MAX_VALUE; + var tx = Math.abs( tangents[ 0 ].x ); + var ty = Math.abs( tangents[ 0 ].y ); + var tz = Math.abs( tangents[ 0 ].z ); + + if ( tx <= min ) { + + min = tx; + normal.set( 1, 0, 0 ); + + } + + if ( ty <= min ) { + + min = ty; + normal.set( 0, 1, 0 ); + + } + + if ( tz <= min ) { + + normal.set( 0, 0, 1 ); + + } + + vec.crossVectors( tangents[ 0 ], normal ).normalize(); + + normals[ 0 ].crossVectors( tangents[ 0 ], vec ); + binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ); + + + // compute the slowly-varying normal and binormal vectors for each segment on the curve + + for ( i = 1; i <= segments; i ++ ) { + + normals[ i ] = normals[ i - 1 ].clone(); + + binormals[ i ] = binormals[ i - 1 ].clone(); + + vec.crossVectors( tangents[ i - 1 ], tangents[ i ] ); + + if ( vec.length() > Number.EPSILON ) { + + vec.normalize(); + + theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors + + normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) ); + + } + + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same + + if ( closed === true ) { + + theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) ); + theta /= segments; + + if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) { + + theta = - theta; + + } + + for ( i = 1; i <= segments; i ++ ) { + + // twist a little... + normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) ); + binormals[ i ].crossVectors( tangents[ i ], normals[ i ] ); + + } + + } + + return { + tangents: tangents, + normals: normals, + binormals: binormals + }; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( source ) { + + this.arcLengthDivisions = source.arcLengthDivisions; + + return this; + + }, + + toJSON: function () { + + var data = { + metadata: { + version: 4.5, + type: 'Curve', + generator: 'Curve.toJSON' + } + }; + + data.arcLengthDivisions = this.arcLengthDivisions; + data.type = this.type; + + return data; + + }, + + fromJSON: function ( json ) { + + this.arcLengthDivisions = json.arcLengthDivisions; + + return this; + + } + + } ); + + function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + + Curve.call( this ); + + this.type = 'EllipseCurve'; + + this.aX = aX || 0; + this.aY = aY || 0; + + this.xRadius = xRadius || 1; + this.yRadius = yRadius || 1; + + this.aStartAngle = aStartAngle || 0; + this.aEndAngle = aEndAngle || 2 * Math.PI; + + this.aClockwise = aClockwise || false; + + this.aRotation = aRotation || 0; + + } + + EllipseCurve.prototype = Object.create( Curve.prototype ); + EllipseCurve.prototype.constructor = EllipseCurve; + + EllipseCurve.prototype.isEllipseCurve = true; + + EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector2(); + + var twoPi = Math.PI * 2; + var deltaAngle = this.aEndAngle - this.aStartAngle; + var samePoints = Math.abs( deltaAngle ) < Number.EPSILON; + + // ensures that deltaAngle is 0 .. 2 PI + while ( deltaAngle < 0 ) deltaAngle += twoPi; + while ( deltaAngle > twoPi ) deltaAngle -= twoPi; + + if ( deltaAngle < Number.EPSILON ) { + + if ( samePoints ) { + + deltaAngle = 0; + + } else { + + deltaAngle = twoPi; + + } + + } + + if ( this.aClockwise === true && ! samePoints ) { + + if ( deltaAngle === twoPi ) { + + deltaAngle = - twoPi; + + } else { + + deltaAngle = deltaAngle - twoPi; + + } + + } + + var angle = this.aStartAngle + t * deltaAngle; + var x = this.aX + this.xRadius * Math.cos( angle ); + var y = this.aY + this.yRadius * Math.sin( angle ); + + if ( this.aRotation !== 0 ) { + + var cos = Math.cos( this.aRotation ); + var sin = Math.sin( this.aRotation ); + + var tx = x - this.aX; + var ty = y - this.aY; + + // Rotate the point about the center of the ellipse. + x = tx * cos - ty * sin + this.aX; + y = tx * sin + ty * cos + this.aY; + + } + + return point.set( x, y ); + + }; + + EllipseCurve.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.aX = source.aX; + this.aY = source.aY; + + this.xRadius = source.xRadius; + this.yRadius = source.yRadius; + + this.aStartAngle = source.aStartAngle; + this.aEndAngle = source.aEndAngle; + + this.aClockwise = source.aClockwise; + + this.aRotation = source.aRotation; + + return this; + + }; + + + EllipseCurve.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.aX = this.aX; + data.aY = this.aY; + + data.xRadius = this.xRadius; + data.yRadius = this.yRadius; + + data.aStartAngle = this.aStartAngle; + data.aEndAngle = this.aEndAngle; + + data.aClockwise = this.aClockwise; + + data.aRotation = this.aRotation; + + return data; + + }; + + EllipseCurve.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.aX = json.aX; + this.aY = json.aY; + + this.xRadius = json.xRadius; + this.yRadius = json.yRadius; + + this.aStartAngle = json.aStartAngle; + this.aEndAngle = json.aEndAngle; + + this.aClockwise = json.aClockwise; + + this.aRotation = json.aRotation; + + return this; + + }; + + function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + + this.type = 'ArcCurve'; + + } + + ArcCurve.prototype = Object.create( EllipseCurve.prototype ); + ArcCurve.prototype.constructor = ArcCurve; + + ArcCurve.prototype.isArcCurve = true; + + /** + * @author zz85 https://github.com/zz85 + * + * Centripetal CatmullRom Curve - which is useful for avoiding + * cusps and self-intersections in non-uniform catmull rom curves. + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * curve.type accepts centripetal(default), chordal and catmullrom + * curve.tension is used for catmullrom which defaults to 0.5 + */ + + + /* + Based on an optimized c++ solution in + - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/ + - http://ideone.com/NoEbVM + + This CubicPoly class could be used for reusing some variables and calculations, + but for three.js curve use, it could be possible inlined and flatten into a single function call + which can be placed in CurveUtils. + */ + + function CubicPoly() { + + var c0 = 0, c1 = 0, c2 = 0, c3 = 0; + + /* + * Compute coefficients for a cubic polynomial + * p(s) = c0 + c1*s + c2*s^2 + c3*s^3 + * such that + * p(0) = x0, p(1) = x1 + * and + * p'(0) = t0, p'(1) = t1. + */ + function init( x0, x1, t0, t1 ) { + + c0 = x0; + c1 = t0; + c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1; + c3 = 2 * x0 - 2 * x1 + t0 + t1; + + } + + return { + + initCatmullRom: function ( x0, x1, x2, x3, tension ) { + + init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) ); + + }, + + initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) { + + // compute tangents when parameterized in [t1,t2] + var t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1; + var t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2; + + // rescale tangents for parametrization in [0,1] + t1 *= dt1; + t2 *= dt1; + + init( x1, x2, t1, t2 ); + + }, + + calc: function ( t ) { + + var t2 = t * t; + var t3 = t2 * t; + return c0 + c1 * t + c2 * t2 + c3 * t3; + + } + + }; + + } + + // + + var tmp = new Vector3(); + var px = new CubicPoly(); + var py = new CubicPoly(); + var pz = new CubicPoly(); + + function CatmullRomCurve3( points, closed, curveType, tension ) { + + Curve.call( this ); + + this.type = 'CatmullRomCurve3'; + + this.points = points || []; + this.closed = closed || false; + this.curveType = curveType || 'centripetal'; + this.tension = tension || 0.5; + + } + + CatmullRomCurve3.prototype = Object.create( Curve.prototype ); + CatmullRomCurve3.prototype.constructor = CatmullRomCurve3; + + CatmullRomCurve3.prototype.isCatmullRomCurve3 = true; + + CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector3(); + + var points = this.points; + var l = points.length; + + var p = ( l - ( this.closed ? 0 : 1 ) ) * t; + var intPoint = Math.floor( p ); + var weight = p - intPoint; + + if ( this.closed ) { + + intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length; + + } else if ( weight === 0 && intPoint === l - 1 ) { + + intPoint = l - 2; + weight = 1; + + } + + var p0, p1, p2, p3; // 4 points + + if ( this.closed || intPoint > 0 ) { + + p0 = points[ ( intPoint - 1 ) % l ]; + + } else { + + // extrapolate first point + tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] ); + p0 = tmp; + + } + + p1 = points[ intPoint % l ]; + p2 = points[ ( intPoint + 1 ) % l ]; + + if ( this.closed || intPoint + 2 < l ) { + + p3 = points[ ( intPoint + 2 ) % l ]; + + } else { + + // extrapolate last point + tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] ); + p3 = tmp; + + } + + if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) { + + // init Centripetal / Chordal Catmull-Rom + var pow = this.curveType === 'chordal' ? 0.5 : 0.25; + var dt0 = Math.pow( p0.distanceToSquared( p1 ), pow ); + var dt1 = Math.pow( p1.distanceToSquared( p2 ), pow ); + var dt2 = Math.pow( p2.distanceToSquared( p3 ), pow ); + + // safety check for repeated points + if ( dt1 < 1e-4 ) dt1 = 1.0; + if ( dt0 < 1e-4 ) dt0 = dt1; + if ( dt2 < 1e-4 ) dt2 = dt1; + + px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 ); + py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 ); + pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 ); + + } else if ( this.curveType === 'catmullrom' ) { + + px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension ); + py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension ); + pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension ); + + } + + point.set( + px.calc( weight ), + py.calc( weight ), + pz.calc( weight ) + ); + + return point; + + }; + + CatmullRomCurve3.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.points = []; + + for ( var i = 0, l = source.points.length; i < l; i ++ ) { + + var point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + this.closed = source.closed; + this.curveType = source.curveType; + this.tension = source.tension; + + return this; + + }; + + CatmullRomCurve3.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.points = []; + + for ( var i = 0, l = this.points.length; i < l; i ++ ) { + + var point = this.points[ i ]; + data.points.push( point.toArray() ); + + } + + data.closed = this.closed; + data.curveType = this.curveType; + data.tension = this.tension; + + return data; + + }; + + CatmullRomCurve3.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.points = []; + + for ( var i = 0, l = json.points.length; i < l; i ++ ) { + + var point = json.points[ i ]; + this.points.push( new Vector3().fromArray( point ) ); + + } + + this.closed = json.closed; + this.curveType = json.curveType; + this.tension = json.tension; + + return this; + + }; + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + * Bezier Curves formulas obtained from + * http://en.wikipedia.org/wiki/Bézier_curve + */ + + function CatmullRom( t, p0, p1, p2, p3 ) { + + var v0 = ( p2 - p0 ) * 0.5; + var v1 = ( p3 - p1 ) * 0.5; + var t2 = t * t; + var t3 = t * t2; + return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1; + + } + + // + + function QuadraticBezierP0( t, p ) { + + var k = 1 - t; + return k * k * p; + + } + + function QuadraticBezierP1( t, p ) { + + return 2 * ( 1 - t ) * t * p; + + } + + function QuadraticBezierP2( t, p ) { + + return t * t * p; + + } + + function QuadraticBezier( t, p0, p1, p2 ) { + + return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) + + QuadraticBezierP2( t, p2 ); + + } + + // + + function CubicBezierP0( t, p ) { + + var k = 1 - t; + return k * k * k * p; + + } + + function CubicBezierP1( t, p ) { + + var k = 1 - t; + return 3 * k * k * t * p; + + } + + function CubicBezierP2( t, p ) { + + return 3 * ( 1 - t ) * t * t * p; + + } + + function CubicBezierP3( t, p ) { + + return t * t * t * p; + + } + + function CubicBezier( t, p0, p1, p2, p3 ) { + + return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) + + CubicBezierP3( t, p3 ); + + } + + function CubicBezierCurve( v0, v1, v2, v3 ) { + + Curve.call( this ); + + this.type = 'CubicBezierCurve'; + + this.v0 = v0 || new Vector2(); + this.v1 = v1 || new Vector2(); + this.v2 = v2 || new Vector2(); + this.v3 = v3 || new Vector2(); + + } + + CubicBezierCurve.prototype = Object.create( Curve.prototype ); + CubicBezierCurve.prototype.constructor = CubicBezierCurve; + + CubicBezierCurve.prototype.isCubicBezierCurve = true; + + CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector2(); + + var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ) + ); + + return point; + + }; + + CubicBezierCurve.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); + + return this; + + }; + + CubicBezierCurve.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); + + return data; + + }; + + CubicBezierCurve.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); + + return this; + + }; + + function CubicBezierCurve3( v0, v1, v2, v3 ) { + + Curve.call( this ); + + this.type = 'CubicBezierCurve3'; + + this.v0 = v0 || new Vector3(); + this.v1 = v1 || new Vector3(); + this.v2 = v2 || new Vector3(); + this.v3 = v3 || new Vector3(); + + } + + CubicBezierCurve3.prototype = Object.create( Curve.prototype ); + CubicBezierCurve3.prototype.constructor = CubicBezierCurve3; + + CubicBezierCurve3.prototype.isCubicBezierCurve3 = true; + + CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector3(); + + var v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3; + + point.set( + CubicBezier( t, v0.x, v1.x, v2.x, v3.x ), + CubicBezier( t, v0.y, v1.y, v2.y, v3.y ), + CubicBezier( t, v0.z, v1.z, v2.z, v3.z ) + ); + + return point; + + }; + + CubicBezierCurve3.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + this.v3.copy( source.v3 ); + + return this; + + }; + + CubicBezierCurve3.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + data.v3 = this.v3.toArray(); + + return data; + + }; + + CubicBezierCurve3.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + this.v3.fromArray( json.v3 ); + + return this; + + }; + + function LineCurve( v1, v2 ) { + + Curve.call( this ); + + this.type = 'LineCurve'; + + this.v1 = v1 || new Vector2(); + this.v2 = v2 || new Vector2(); + + } + + LineCurve.prototype = Object.create( Curve.prototype ); + LineCurve.prototype.constructor = LineCurve; + + LineCurve.prototype.isLineCurve = true; + + LineCurve.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector2(); + + if ( t === 1 ) { + + point.copy( this.v2 ); + + } else { + + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); + + } + + return point; + + }; + + // Line curve is linear, so we can overwrite default getPointAt + + LineCurve.prototype.getPointAt = function ( u, optionalTarget ) { + + return this.getPoint( u, optionalTarget ); + + }; + + LineCurve.prototype.getTangent = function ( /* t */ ) { + + var tangent = this.v2.clone().sub( this.v1 ); + + return tangent.normalize(); + + }; + + LineCurve.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + }; + + LineCurve.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + }; + + LineCurve.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + }; + + function LineCurve3( v1, v2 ) { + + Curve.call( this ); + + this.type = 'LineCurve3'; + + this.v1 = v1 || new Vector3(); + this.v2 = v2 || new Vector3(); + + } + + LineCurve3.prototype = Object.create( Curve.prototype ); + LineCurve3.prototype.constructor = LineCurve3; + + LineCurve3.prototype.isLineCurve3 = true; + + LineCurve3.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector3(); + + if ( t === 1 ) { + + point.copy( this.v2 ); + + } else { + + point.copy( this.v2 ).sub( this.v1 ); + point.multiplyScalar( t ).add( this.v1 ); + + } + + return point; + + }; + + // Line curve is linear, so we can overwrite default getPointAt + + LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) { + + return this.getPoint( u, optionalTarget ); + + }; + + LineCurve3.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + }; + + LineCurve3.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + }; + + LineCurve3.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + }; + + function QuadraticBezierCurve( v0, v1, v2 ) { + + Curve.call( this ); + + this.type = 'QuadraticBezierCurve'; + + this.v0 = v0 || new Vector2(); + this.v1 = v1 || new Vector2(); + this.v2 = v2 || new Vector2(); + + } + + QuadraticBezierCurve.prototype = Object.create( Curve.prototype ); + QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve; + + QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true; + + QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector2(); + + var v0 = this.v0, v1 = this.v1, v2 = this.v2; + + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ) + ); + + return point; + + }; + + QuadraticBezierCurve.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + }; + + QuadraticBezierCurve.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + }; + + QuadraticBezierCurve.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + }; + + function QuadraticBezierCurve3( v0, v1, v2 ) { + + Curve.call( this ); + + this.type = 'QuadraticBezierCurve3'; + + this.v0 = v0 || new Vector3(); + this.v1 = v1 || new Vector3(); + this.v2 = v2 || new Vector3(); + + } + + QuadraticBezierCurve3.prototype = Object.create( Curve.prototype ); + QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3; + + QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true; + + QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector3(); + + var v0 = this.v0, v1 = this.v1, v2 = this.v2; + + point.set( + QuadraticBezier( t, v0.x, v1.x, v2.x ), + QuadraticBezier( t, v0.y, v1.y, v2.y ), + QuadraticBezier( t, v0.z, v1.z, v2.z ) + ); + + return point; + + }; + + QuadraticBezierCurve3.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.v0.copy( source.v0 ); + this.v1.copy( source.v1 ); + this.v2.copy( source.v2 ); + + return this; + + }; + + QuadraticBezierCurve3.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.v0 = this.v0.toArray(); + data.v1 = this.v1.toArray(); + data.v2 = this.v2.toArray(); + + return data; + + }; + + QuadraticBezierCurve3.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.v0.fromArray( json.v0 ); + this.v1.fromArray( json.v1 ); + this.v2.fromArray( json.v2 ); + + return this; + + }; + + function SplineCurve( points /* array of Vector2 */ ) { + + Curve.call( this ); + + this.type = 'SplineCurve'; + + this.points = points || []; + + } + + SplineCurve.prototype = Object.create( Curve.prototype ); + SplineCurve.prototype.constructor = SplineCurve; + + SplineCurve.prototype.isSplineCurve = true; + + SplineCurve.prototype.getPoint = function ( t, optionalTarget ) { + + var point = optionalTarget || new Vector2(); + + var points = this.points; + var p = ( points.length - 1 ) * t; + + var intPoint = Math.floor( p ); + var weight = p - intPoint; + + var p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ]; + var p1 = points[ intPoint ]; + var p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ]; + var p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ]; + + point.set( + CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ), + CatmullRom( weight, p0.y, p1.y, p2.y, p3.y ) + ); + + return point; + + }; + + SplineCurve.prototype.copy = function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.points = []; + + for ( var i = 0, l = source.points.length; i < l; i ++ ) { + + var point = source.points[ i ]; + + this.points.push( point.clone() ); + + } + + return this; + + }; + + SplineCurve.prototype.toJSON = function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.points = []; + + for ( var i = 0, l = this.points.length; i < l; i ++ ) { + + var point = this.points[ i ]; + data.points.push( point.toArray() ); + + } + + return data; + + }; + + SplineCurve.prototype.fromJSON = function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.points = []; + + for ( var i = 0, l = json.points.length; i < l; i ++ ) { + + var point = json.points[ i ]; + this.points.push( new Vector2().fromArray( point ) ); + + } + + return this; + + }; + + + + var Curves = Object.freeze({ + ArcCurve: ArcCurve, + CatmullRomCurve3: CatmullRomCurve3, + CubicBezierCurve: CubicBezierCurve, + CubicBezierCurve3: CubicBezierCurve3, + EllipseCurve: EllipseCurve, + LineCurve: LineCurve, + LineCurve3: LineCurve3, + QuadraticBezierCurve: QuadraticBezierCurve, + QuadraticBezierCurve3: QuadraticBezierCurve3, + SplineCurve: SplineCurve + }); + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * + **/ + + /************************************************************** + * Curved Path - a curve path is simply a array of connected + * curves, but retains the api of a curve + **************************************************************/ + + function CurvePath() { + + Curve.call( this ); + + this.type = 'CurvePath'; + + this.curves = []; + this.autoClose = false; // Automatically closes the path + + } + + CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), { + + constructor: CurvePath, + + add: function ( curve ) { + + this.curves.push( curve ); + + }, + + closePath: function () { + + // Add a line curve if start and end of lines are not connected + var startPoint = this.curves[ 0 ].getPoint( 0 ); + var endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 ); + + if ( ! startPoint.equals( endPoint ) ) { + + this.curves.push( new LineCurve( endPoint, startPoint ) ); + + } + + }, + + // To get accurate point with reference to + // entire path distance at time t, + // following has to be done: + + // 1. Length of each sub path have to be known + // 2. Locate and identify type of curve + // 3. Get t for the curve + // 4. Return curve.getPointAt(t') + + getPoint: function ( t ) { + + var d = t * this.getLength(); + var curveLengths = this.getCurveLengths(); + var i = 0; + + // To think about boundaries points. + + while ( i < curveLengths.length ) { + + if ( curveLengths[ i ] >= d ) { + + var diff = curveLengths[ i ] - d; + var curve = this.curves[ i ]; + + var segmentLength = curve.getLength(); + var u = segmentLength === 0 ? 0 : 1 - diff / segmentLength; + + return curve.getPointAt( u ); + + } + + i ++; + + } + + return null; + + // loop where sum != 0, sum > d , sum+1 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) { + + points.push( points[ 0 ] ); + + } + + return points; + + }, + + copy: function ( source ) { + + Curve.prototype.copy.call( this, source ); + + this.curves = []; + + for ( var i = 0, l = source.curves.length; i < l; i ++ ) { + + var curve = source.curves[ i ]; + + this.curves.push( curve.clone() ); + + } + + this.autoClose = source.autoClose; + + return this; + + }, + + toJSON: function () { + + var data = Curve.prototype.toJSON.call( this ); + + data.autoClose = this.autoClose; + data.curves = []; + + for ( var i = 0, l = this.curves.length; i < l; i ++ ) { + + var curve = this.curves[ i ]; + data.curves.push( curve.toJSON() ); + + } + + return data; + + }, + + fromJSON: function ( json ) { + + Curve.prototype.fromJSON.call( this, json ); + + this.autoClose = json.autoClose; + this.curves = []; + + for ( var i = 0, l = json.curves.length; i < l; i ++ ) { + + var curve = json.curves[ i ]; + this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) ); + + } + + return this; + + } + + } ); + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Creates free form 2d path using series of points, lines or curves. + **/ + + function Path( points ) { + + CurvePath.call( this ); + + this.type = 'Path'; + + this.currentPoint = new Vector2(); + + if ( points ) { + + this.setFromPoints( points ); + + } + + } + + Path.prototype = Object.assign( Object.create( CurvePath.prototype ), { + + constructor: Path, + + setFromPoints: function ( points ) { + + this.moveTo( points[ 0 ].x, points[ 0 ].y ); + + for ( var i = 1, l = points.length; i < l; i ++ ) { + + this.lineTo( points[ i ].x, points[ i ].y ); + + } + + }, + + moveTo: function ( x, y ) { + + this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying? + + }, + + lineTo: function ( x, y ) { + + var curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) ); + this.curves.push( curve ); + + this.currentPoint.set( x, y ); + + }, + + quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) { + + var curve = new QuadraticBezierCurve( + this.currentPoint.clone(), + new Vector2( aCPx, aCPy ), + new Vector2( aX, aY ) + ); + + this.curves.push( curve ); + + this.currentPoint.set( aX, aY ); + + }, + + bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + + var curve = new CubicBezierCurve( + this.currentPoint.clone(), + new Vector2( aCP1x, aCP1y ), + new Vector2( aCP2x, aCP2y ), + new Vector2( aX, aY ) + ); + + this.curves.push( curve ); + + this.currentPoint.set( aX, aY ); + + }, + + splineThru: function ( pts /*Array of Vector*/ ) { + + var npts = [ this.currentPoint.clone() ].concat( pts ); + + var curve = new SplineCurve( npts ); + this.curves.push( curve ); + + this.currentPoint.copy( pts[ pts.length - 1 ] ); + + }, + + arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + var x0 = this.currentPoint.x; + var y0 = this.currentPoint.y; + + this.absarc( aX + x0, aY + y0, aRadius, + aStartAngle, aEndAngle, aClockwise ); + + }, + + absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) { + + this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise ); + + }, + + ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + + var x0 = this.currentPoint.x; + var y0 = this.currentPoint.y; + + this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + + }, + + absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) { + + var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ); + + if ( this.curves.length > 0 ) { + + // if a previous curve is present, attempt to join + var firstPoint = curve.getPoint( 0 ); + + if ( ! firstPoint.equals( this.currentPoint ) ) { + + this.lineTo( firstPoint.x, firstPoint.y ); + + } + + } + + this.curves.push( curve ); + + var lastPoint = curve.getPoint( 1 ); + this.currentPoint.copy( lastPoint ); + + }, + + copy: function ( source ) { + + CurvePath.prototype.copy.call( this, source ); + + this.currentPoint.copy( source.currentPoint ); + + return this; + + }, + + toJSON: function () { + + var data = CurvePath.prototype.toJSON.call( this ); + + data.currentPoint = this.currentPoint.toArray(); + + return data; + + }, + + fromJSON: function ( json ) { + + CurvePath.prototype.fromJSON.call( this, json ); + + this.currentPoint.fromArray( json.currentPoint ); + + return this; + + } + + } ); + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * Defines a 2d shape plane using paths. + **/ + + // STEP 1 Create a path. + // STEP 2 Turn path into shape. + // STEP 3 ExtrudeGeometry takes in Shape/Shapes + // STEP 3a - Extract points from each shape, turn to vertices + // STEP 3b - Triangulate each shape, add faces. + + function Shape( points ) { + + Path.call( this, points ); + + this.uuid = _Math.generateUUID(); + + this.type = 'Shape'; + + this.holes = []; + + } + + Shape.prototype = Object.assign( Object.create( Path.prototype ), { + + constructor: Shape, + + getPointsHoles: function ( divisions ) { + + var holesPts = []; + + for ( var i = 0, l = this.holes.length; i < l; i ++ ) { + + holesPts[ i ] = this.holes[ i ].getPoints( divisions ); + + } + + return holesPts; + + }, + + // get points of shape and holes (keypoints based on segments parameter) + + extractPoints: function ( divisions ) { + + return { + + shape: this.getPoints( divisions ), + holes: this.getPointsHoles( divisions ) + + }; + + }, + + copy: function ( source ) { + + Path.prototype.copy.call( this, source ); + + this.holes = []; + + for ( var i = 0, l = source.holes.length; i < l; i ++ ) { + + var hole = source.holes[ i ]; + + this.holes.push( hole.clone() ); + + } + + return this; + + }, + + toJSON: function () { + + var data = Path.prototype.toJSON.call( this ); + + data.uuid = this.uuid; + data.holes = []; + + for ( var i = 0, l = this.holes.length; i < l; i ++ ) { + + var hole = this.holes[ i ]; + data.holes.push( hole.toJSON() ); + + } + + return data; + + }, + + fromJSON: function ( json ) { + + Path.prototype.fromJSON.call( this, json ); + + this.uuid = json.uuid; + this.holes = []; + + for ( var i = 0, l = json.holes.length; i < l; i ++ ) { + + var hole = json.holes[ i ]; + this.holes.push( new Path().fromJSON( hole ) ); + + } + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function Light( color, intensity ) { + + Object3D.call( this ); + + this.type = 'Light'; + + this.color = new Color( color ); + this.intensity = intensity !== undefined ? intensity : 1; + + this.receiveShadow = undefined; + + } + + Light.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Light, + + isLight: true, + + copy: function ( source ) { + + Object3D.prototype.copy.call( this, source ); + + this.color.copy( source.color ); + this.intensity = source.intensity; + + return this; + + }, + + toJSON: function ( meta ) { + + var data = Object3D.prototype.toJSON.call( this, meta ); + + data.object.color = this.color.getHex(); + data.object.intensity = this.intensity; + + if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex(); + + if ( this.distance !== undefined ) data.object.distance = this.distance; + if ( this.angle !== undefined ) data.object.angle = this.angle; + if ( this.decay !== undefined ) data.object.decay = this.decay; + if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra; + + if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON(); + + return data; + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function HemisphereLight( skyColor, groundColor, intensity ) { + + Light.call( this, skyColor, intensity ); + + this.type = 'HemisphereLight'; + + this.castShadow = undefined; + + this.position.copy( Object3D.DefaultUp ); + this.updateMatrix(); + + this.groundColor = new Color( groundColor ); + + } + + HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: HemisphereLight, + + isHemisphereLight: true, + + copy: function ( source ) { + + Light.prototype.copy.call( this, source ); + + this.groundColor.copy( source.groundColor ); + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function LightShadow( camera ) { + + this.camera = camera; + + this.bias = 0; + this.radius = 1; + + this.mapSize = new Vector2( 512, 512 ); + + this.map = null; + this.matrix = new Matrix4(); + + } + + Object.assign( LightShadow.prototype, { + + copy: function ( source ) { + + this.camera = source.camera.clone(); + + this.bias = source.bias; + this.radius = source.radius; + + this.mapSize.copy( source.mapSize ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + toJSON: function () { + + var object = {}; + + if ( this.bias !== 0 ) object.bias = this.bias; + if ( this.radius !== 1 ) object.radius = this.radius; + if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray(); + + object.camera = this.camera.toJSON( false ).object; + delete object.camera.matrix; + + return object; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function SpotLightShadow() { + + LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) ); + + } + + SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), { + + constructor: SpotLightShadow, + + isSpotLightShadow: true, + + update: function ( light ) { + + var camera = this.camera; + + var fov = _Math.RAD2DEG * 2 * light.angle; + var aspect = this.mapSize.width / this.mapSize.height; + var far = light.distance || camera.far; + + if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) { + + camera.fov = fov; + camera.aspect = aspect; + camera.far = far; + camera.updateProjectionMatrix(); + + } + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function SpotLight( color, intensity, distance, angle, penumbra, decay ) { + + Light.call( this, color, intensity ); + + this.type = 'SpotLight'; + + this.position.copy( Object3D.DefaultUp ); + this.updateMatrix(); + + this.target = new Object3D(); + + Object.defineProperty( this, 'power', { + get: function () { + + // intensity = power per solid angle. + // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + return this.intensity * Math.PI; + + }, + set: function ( power ) { + + // intensity = power per solid angle. + // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + this.intensity = power / Math.PI; + + } + } ); + + this.distance = ( distance !== undefined ) ? distance : 0; + this.angle = ( angle !== undefined ) ? angle : Math.PI / 3; + this.penumbra = ( penumbra !== undefined ) ? penumbra : 0; + this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. + + this.shadow = new SpotLightShadow(); + + } + + SpotLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: SpotLight, + + isSpotLight: true, + + copy: function ( source ) { + + Light.prototype.copy.call( this, source ); + + this.distance = source.distance; + this.angle = source.angle; + this.penumbra = source.penumbra; + this.decay = source.decay; + + this.target = source.target.clone(); + + this.shadow = source.shadow.clone(); + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + + function PointLight( color, intensity, distance, decay ) { + + Light.call( this, color, intensity ); + + this.type = 'PointLight'; + + Object.defineProperty( this, 'power', { + get: function () { + + // intensity = power per solid angle. + // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + return this.intensity * 4 * Math.PI; + + }, + set: function ( power ) { + + // intensity = power per solid angle. + // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + this.intensity = power / ( 4 * Math.PI ); + + } + } ); + + this.distance = ( distance !== undefined ) ? distance : 0; + this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2. + + this.shadow = new LightShadow( new PerspectiveCamera( 90, 1, 0.5, 500 ) ); + + } + + PointLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: PointLight, + + isPointLight: true, + + copy: function ( source ) { + + Light.prototype.copy.call( this, source ); + + this.distance = source.distance; + this.decay = source.decay; + + this.shadow = source.shadow.clone(); + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function DirectionalLightShadow( ) { + + LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) ); + + } + + DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), { + + constructor: DirectionalLightShadow + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function DirectionalLight( color, intensity ) { + + Light.call( this, color, intensity ); + + this.type = 'DirectionalLight'; + + this.position.copy( Object3D.DefaultUp ); + this.updateMatrix(); + + this.target = new Object3D(); + + this.shadow = new DirectionalLightShadow(); + + } + + DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: DirectionalLight, + + isDirectionalLight: true, + + copy: function ( source ) { + + Light.prototype.copy.call( this, source ); + + this.target = source.target.clone(); + + this.shadow = source.shadow.clone(); + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function AmbientLight( color, intensity ) { + + Light.call( this, color, intensity ); + + this.type = 'AmbientLight'; + + this.castShadow = undefined; + + } + + AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: AmbientLight, + + isAmbientLight: true + + } ); + + /** + * @author abelnation / http://github.com/abelnation + */ + + function RectAreaLight( color, intensity, width, height ) { + + Light.call( this, color, intensity ); + + this.type = 'RectAreaLight'; + + this.width = ( width !== undefined ) ? width : 10; + this.height = ( height !== undefined ) ? height : 10; + + } + + RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), { + + constructor: RectAreaLight, + + isRectAreaLight: true, + + copy: function ( source ) { + + Light.prototype.copy.call( this, source ); + + this.width = source.width; + this.height = source.height; + + return this; + + }, + + toJSON: function ( meta ) { + + var data = Light.prototype.toJSON.call( this, meta ); + + data.object.width = this.width; + data.object.height = this.height; + + return data; + + } + + } ); + + /** + * + * A Track that interpolates Strings + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function StringKeyframeTrack( name, times, values, interpolation ) { + + KeyframeTrack.call( this, name, times, values, interpolation ); + + } + + StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: StringKeyframeTrack, + + ValueTypeName: 'string', + ValueBufferType: Array, + + DefaultInterpolation: InterpolateDiscrete, + + InterpolantFactoryMethodLinear: undefined, + + InterpolantFactoryMethodSmooth: undefined + + } ); + + /** + * + * A Track of Boolean keyframe values. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function BooleanKeyframeTrack( name, times, values ) { + + KeyframeTrack.call( this, name, times, values ); + + } + + BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: BooleanKeyframeTrack, + + ValueTypeName: 'bool', + ValueBufferType: Array, + + DefaultInterpolation: InterpolateDiscrete, + + InterpolantFactoryMethodLinear: undefined, + InterpolantFactoryMethodSmooth: undefined + + // Note: Actually this track could have a optimized / compressed + // representation of a single value and a custom interpolant that + // computes "firstValue ^ isOdd( index )". + + } ); + + /** + * Abstract base class of interpolants over parametric samples. + * + * The parameter domain is one dimensional, typically the time or a path + * along a curve defined by the data. + * + * The sample values can have any dimensionality and derived classes may + * apply special interpretations to the data. + * + * This class provides the interval seek in a Template Method, deferring + * the actual interpolation to derived classes. + * + * Time complexity is O(1) for linear access crossing at most two points + * and O(log N) for random access, where N is the number of positions. + * + * References: + * + * http://www.oodesign.com/template-method-pattern.html + * + * @author tschw + */ + + function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + this.parameterPositions = parameterPositions; + this._cachedIndex = 0; + + this.resultBuffer = resultBuffer !== undefined ? + resultBuffer : new sampleValues.constructor( sampleSize ); + this.sampleValues = sampleValues; + this.valueSize = sampleSize; + + } + + Object.assign( Interpolant.prototype, { + + evaluate: function ( t ) { + + var pp = this.parameterPositions, + i1 = this._cachedIndex, + + t1 = pp[ i1 ], + t0 = pp[ i1 - 1 ]; + + validate_interval: { + + seek: { + + var right; + + linear_scan: { + + //- See http://jsperf.com/comparison-to-undefined/3 + //- slower code: + //- + //- if ( t >= t1 || t1 === undefined ) { + forward_scan: if ( ! ( t < t1 ) ) { + + for ( var giveUpAt = i1 + 2; ; ) { + + if ( t1 === undefined ) { + + if ( t < t0 ) break forward_scan; + + // after end + + i1 = pp.length; + this._cachedIndex = i1; + return this.afterEnd_( i1 - 1, t, t0 ); + + } + + if ( i1 === giveUpAt ) break; // this loop + + t0 = t1; + t1 = pp[ ++ i1 ]; + + if ( t < t1 ) { + + // we have arrived at the sought interval + break seek; + + } + + } + + // prepare binary search on the right side of the index + right = pp.length; + break linear_scan; + + } + + //- slower code: + //- if ( t < t0 || t0 === undefined ) { + if ( ! ( t >= t0 ) ) { + + // looping? + + var t1global = pp[ 1 ]; + + if ( t < t1global ) { + + i1 = 2; // + 1, using the scan for the details + t0 = t1global; + + } + + // linear reverse scan + + for ( var giveUpAt = i1 - 2; ; ) { + + if ( t0 === undefined ) { + + // before start + + this._cachedIndex = 0; + return this.beforeStart_( 0, t, t1 ); + + } + + if ( i1 === giveUpAt ) break; // this loop + + t1 = t0; + t0 = pp[ -- i1 - 1 ]; + + if ( t >= t0 ) { + + // we have arrived at the sought interval + break seek; + + } + + } + + // prepare binary search on the left side of the index + right = i1; + i1 = 0; + break linear_scan; + + } + + // the interval is valid + + break validate_interval; + + } // linear scan + + // binary search + + while ( i1 < right ) { + + var mid = ( i1 + right ) >>> 1; + + if ( t < pp[ mid ] ) { + + right = mid; + + } else { + + i1 = mid + 1; + + } + + } + + t1 = pp[ i1 ]; + t0 = pp[ i1 - 1 ]; + + // check boundary cases, again + + if ( t0 === undefined ) { + + this._cachedIndex = 0; + return this.beforeStart_( 0, t, t1 ); + + } + + if ( t1 === undefined ) { + + i1 = pp.length; + this._cachedIndex = i1; + return this.afterEnd_( i1 - 1, t0, t ); + + } + + } // seek + + this._cachedIndex = i1; + + this.intervalChanged_( i1, t0, t1 ); + + } // validate_interval + + return this.interpolate_( i1, t0, t, t1 ); + + }, + + settings: null, // optional, subclass-specific settings structure + // Note: The indirection allows central control of many interpolants. + + // --- Protected interface + + DefaultSettings_: {}, + + getSettings_: function () { + + return this.settings || this.DefaultSettings_; + + }, + + copySampleValue_: function ( index ) { + + // copies a sample value to the result buffer + + var result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + offset = index * stride; + + for ( var i = 0; i !== stride; ++ i ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + }, + + // Template methods for derived classes: + + interpolate_: function ( /* i1, t0, t, t1 */ ) { + + throw new Error( 'call to abstract method' ); + // implementations shall return this.resultBuffer + + }, + + intervalChanged_: function ( /* i1, t0, t1 */ ) { + + // empty + + } + + } ); + + //!\ DECLARE ALIAS AFTER assign prototype ! + Object.assign( Interpolant.prototype, { + + //( 0, t, t0 ), returns this.resultBuffer + beforeStart_: Interpolant.prototype.copySampleValue_, + + //( N-1, tN-1, t ), returns this.resultBuffer + afterEnd_: Interpolant.prototype.copySampleValue_, + + } ); + + /** + * Spherical linear unit quaternion interpolant. + * + * @author tschw + */ + + function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { + + constructor: QuaternionLinearInterpolant, + + interpolate_: function ( i1, t0, t, t1 ) { + + var result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + offset = i1 * stride, + + alpha = ( t - t0 ) / ( t1 - t0 ); + + for ( var end = offset + stride; offset !== end; offset += 4 ) { + + Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha ); + + } + + return result; + + } + + } ); + + /** + * + * A Track of quaternion keyframe values. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function QuaternionKeyframeTrack( name, times, values, interpolation ) { + + KeyframeTrack.call( this, name, times, values, interpolation ); + + } + + QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: QuaternionKeyframeTrack, + + ValueTypeName: 'quaternion', + + // ValueBufferType is inherited + + DefaultInterpolation: InterpolateLinear, + + InterpolantFactoryMethodLinear: function ( result ) { + + return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result ); + + }, + + InterpolantFactoryMethodSmooth: undefined // not yet implemented + + } ); + + /** + * + * A Track of keyframe values that represent color. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function ColorKeyframeTrack( name, times, values, interpolation ) { + + KeyframeTrack.call( this, name, times, values, interpolation ); + + } + + ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: ColorKeyframeTrack, + + ValueTypeName: 'color' + + // ValueBufferType is inherited + + // DefaultInterpolation is inherited + + // Note: Very basic implementation and nothing special yet. + // However, this is the place for color space parameterization. + + } ); + + /** + * + * A Track of numeric keyframe values. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function NumberKeyframeTrack( name, times, values, interpolation ) { + + KeyframeTrack.call( this, name, times, values, interpolation ); + + } + + NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: NumberKeyframeTrack, + + ValueTypeName: 'number' + + // ValueBufferType is inherited + + // DefaultInterpolation is inherited + + } ); + + /** + * Fast and simple cubic spline interpolant. + * + * It was derived from a Hermitian construction setting the first derivative + * at each sample position to the linear slope between neighboring positions + * over their parameter interval. + * + * @author tschw + */ + + function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + this._weightPrev = - 0; + this._offsetPrev = - 0; + this._weightNext = - 0; + this._offsetNext = - 0; + + } + + CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { + + constructor: CubicInterpolant, + + DefaultSettings_: { + + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + + }, + + intervalChanged_: function ( i1, t0, t1 ) { + + var pp = this.parameterPositions, + iPrev = i1 - 2, + iNext = i1 + 1, + + tPrev = pp[ iPrev ], + tNext = pp[ iNext ]; + + if ( tPrev === undefined ) { + + switch ( this.getSettings_().endingStart ) { + + case ZeroSlopeEnding: + + // f'(t0) = 0 + iPrev = i1; + tPrev = 2 * t0 - t1; + + break; + + case WrapAroundEnding: + + // use the other end of the curve + iPrev = pp.length - 2; + tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ]; + + break; + + default: // ZeroCurvatureEnding + + // f''(t0) = 0 a.k.a. Natural Spline + iPrev = i1; + tPrev = t1; + + } + + } + + if ( tNext === undefined ) { + + switch ( this.getSettings_().endingEnd ) { + + case ZeroSlopeEnding: + + // f'(tN) = 0 + iNext = i1; + tNext = 2 * t1 - t0; + + break; + + case WrapAroundEnding: + + // use the other end of the curve + iNext = 1; + tNext = t1 + pp[ 1 ] - pp[ 0 ]; + + break; + + default: // ZeroCurvatureEnding + + // f''(tN) = 0, a.k.a. Natural Spline + iNext = i1 - 1; + tNext = t0; + + } + + } + + var halfDt = ( t1 - t0 ) * 0.5, + stride = this.valueSize; + + this._weightPrev = halfDt / ( t0 - tPrev ); + this._weightNext = halfDt / ( tNext - t1 ); + this._offsetPrev = iPrev * stride; + this._offsetNext = iNext * stride; + + }, + + interpolate_: function ( i1, t0, t, t1 ) { + + var result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + o1 = i1 * stride, o0 = o1 - stride, + oP = this._offsetPrev, oN = this._offsetNext, + wP = this._weightPrev, wN = this._weightNext, + + p = ( t - t0 ) / ( t1 - t0 ), + pp = p * p, + ppp = pp * p; + + // evaluate polynomials + + var sP = - wP * ppp + 2 * wP * pp - wP * p; + var s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1; + var s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p; + var sN = wN * ppp - wN * pp; + + // combine data linearly + + for ( var i = 0; i !== stride; ++ i ) { + + result[ i ] = + sP * values[ oP + i ] + + s0 * values[ o0 + i ] + + s1 * values[ o1 + i ] + + sN * values[ oN + i ]; + + } + + return result; + + } + + } ); + + /** + * @author tschw + */ + + function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { + + constructor: LinearInterpolant, + + interpolate_: function ( i1, t0, t, t1 ) { + + var result = this.resultBuffer, + values = this.sampleValues, + stride = this.valueSize, + + offset1 = i1 * stride, + offset0 = offset1 - stride, + + weight1 = ( t - t0 ) / ( t1 - t0 ), + weight0 = 1 - weight1; + + for ( var i = 0; i !== stride; ++ i ) { + + result[ i ] = + values[ offset0 + i ] * weight0 + + values[ offset1 + i ] * weight1; + + } + + return result; + + } + + } ); + + /** + * + * Interpolant that evaluates to the sample value at the position preceeding + * the parameter. + * + * @author tschw + */ + + function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), { + + constructor: DiscreteInterpolant, + + interpolate_: function ( i1 /*, t0, t, t1 */ ) { + + return this.copySampleValue_( i1 - 1 ); + + } + + } ); + + /** + * @author tschw + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ + + var AnimationUtils = { + + // same as Array.prototype.slice, but also works on typed arrays + arraySlice: function ( array, from, to ) { + + if ( AnimationUtils.isTypedArray( array ) ) { + + // in ios9 array.subarray(from, undefined) will return empty array + // but array.subarray(from) or array.subarray(from, len) is correct + return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) ); + + } + + return array.slice( from, to ); + + }, + + // converts an array to a specific type + convertArray: function ( array, type, forceClone ) { + + if ( ! array || // let 'undefined' and 'null' pass + ! forceClone && array.constructor === type ) return array; + + if ( typeof type.BYTES_PER_ELEMENT === 'number' ) { + + return new type( array ); // create typed array + + } + + return Array.prototype.slice.call( array ); // create Array + + }, + + isTypedArray: function ( object ) { + + return ArrayBuffer.isView( object ) && + ! ( object instanceof DataView ); + + }, + + // returns an array by which times and values can be sorted + getKeyframeOrder: function ( times ) { + + function compareTime( i, j ) { + + return times[ i ] - times[ j ]; + + } + + var n = times.length; + var result = new Array( n ); + for ( var i = 0; i !== n; ++ i ) result[ i ] = i; + + result.sort( compareTime ); + + return result; + + }, + + // uses the array previously returned by 'getKeyframeOrder' to sort data + sortedArray: function ( values, stride, order ) { + + var nValues = values.length; + var result = new values.constructor( nValues ); + + for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) { + + var srcOffset = order[ i ] * stride; + + for ( var j = 0; j !== stride; ++ j ) { + + result[ dstOffset ++ ] = values[ srcOffset + j ]; + + } + + } + + return result; + + }, + + // function for parsing AOS keyframe formats + flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) { + + var i = 1, key = jsonKeys[ 0 ]; + + while ( key !== undefined && key[ valuePropertyName ] === undefined ) { + + key = jsonKeys[ i ++ ]; + + } + + if ( key === undefined ) return; // no data + + var value = key[ valuePropertyName ]; + if ( value === undefined ) return; // no data + + if ( Array.isArray( value ) ) { + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + values.push.apply( values, value ); // push all elements + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } else if ( value.toArray !== undefined ) { + + // ...assume THREE.Math-ish + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + value.toArray( values, values.length ); + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } else { + + // otherwise push as-is + + do { + + value = key[ valuePropertyName ]; + + if ( value !== undefined ) { + + times.push( key.time ); + values.push( value ); + + } + + key = jsonKeys[ i ++ ]; + + } while ( key !== undefined ); + + } + + } + + }; + + /** + * + * A timed sequence of keyframes for a specific property. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function KeyframeTrack( name, times, values, interpolation ) { + + if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' ); + if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name ); + + this.name = name; + + this.times = AnimationUtils.convertArray( times, this.TimeBufferType ); + this.values = AnimationUtils.convertArray( values, this.ValueBufferType ); + + this.setInterpolation( interpolation || this.DefaultInterpolation ); + + this.validate(); + this.optimize(); + + } + + // Static methods: + + Object.assign( KeyframeTrack, { + + // Serialization (in static context, because of constructor invocation + // and automatic invocation of .toJSON): + + parse: function ( json ) { + + if ( json.type === undefined ) { + + throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' ); + + } + + var trackType = KeyframeTrack._getTrackTypeForValueTypeName( json.type ); + + if ( json.times === undefined ) { + + var times = [], values = []; + + AnimationUtils.flattenJSON( json.keys, times, values, 'value' ); + + json.times = times; + json.values = values; + + } + + // derived classes can define a static parse method + if ( trackType.parse !== undefined ) { + + return trackType.parse( json ); + + } else { + + // by default, we assume a constructor compatible with the base + return new trackType( json.name, json.times, json.values, json.interpolation ); + + } + + }, + + toJSON: function ( track ) { + + var trackType = track.constructor; + + var json; + + // derived classes can define a static toJSON method + if ( trackType.toJSON !== undefined ) { + + json = trackType.toJSON( track ); + + } else { + + // by default, we assume the data can be serialized as-is + json = { + + 'name': track.name, + 'times': AnimationUtils.convertArray( track.times, Array ), + 'values': AnimationUtils.convertArray( track.values, Array ) + + }; + + var interpolation = track.getInterpolation(); + + if ( interpolation !== track.DefaultInterpolation ) { + + json.interpolation = interpolation; + + } + + } + + json.type = track.ValueTypeName; // mandatory + + return json; + + }, + + _getTrackTypeForValueTypeName: function ( typeName ) { + + switch ( typeName.toLowerCase() ) { + + case 'scalar': + case 'double': + case 'float': + case 'number': + case 'integer': + + return NumberKeyframeTrack; + + case 'vector': + case 'vector2': + case 'vector3': + case 'vector4': + + return VectorKeyframeTrack; + + case 'color': + + return ColorKeyframeTrack; + + case 'quaternion': + + return QuaternionKeyframeTrack; + + case 'bool': + case 'boolean': + + return BooleanKeyframeTrack; + + case 'string': + + return StringKeyframeTrack; + + } + + throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName ); + + } + + } ); + + Object.assign( KeyframeTrack.prototype, { + + constructor: KeyframeTrack, + + TimeBufferType: Float32Array, + + ValueBufferType: Float32Array, + + DefaultInterpolation: InterpolateLinear, + + InterpolantFactoryMethodDiscrete: function ( result ) { + + return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result ); + + }, + + InterpolantFactoryMethodLinear: function ( result ) { + + return new LinearInterpolant( this.times, this.values, this.getValueSize(), result ); + + }, + + InterpolantFactoryMethodSmooth: function ( result ) { + + return new CubicInterpolant( this.times, this.values, this.getValueSize(), result ); + + }, + + setInterpolation: function ( interpolation ) { + + var factoryMethod; + + switch ( interpolation ) { + + case InterpolateDiscrete: + + factoryMethod = this.InterpolantFactoryMethodDiscrete; + + break; + + case InterpolateLinear: + + factoryMethod = this.InterpolantFactoryMethodLinear; + + break; + + case InterpolateSmooth: + + factoryMethod = this.InterpolantFactoryMethodSmooth; + + break; + + } + + if ( factoryMethod === undefined ) { + + var message = "unsupported interpolation for " + + this.ValueTypeName + " keyframe track named " + this.name; + + if ( this.createInterpolant === undefined ) { + + // fall back to default, unless the default itself is messed up + if ( interpolation !== this.DefaultInterpolation ) { + + this.setInterpolation( this.DefaultInterpolation ); + + } else { + + throw new Error( message ); // fatal, in this case + + } + + } + + console.warn( 'THREE.KeyframeTrack:', message ); + return; + + } + + this.createInterpolant = factoryMethod; + + }, + + getInterpolation: function () { + + switch ( this.createInterpolant ) { + + case this.InterpolantFactoryMethodDiscrete: + + return InterpolateDiscrete; + + case this.InterpolantFactoryMethodLinear: + + return InterpolateLinear; + + case this.InterpolantFactoryMethodSmooth: + + return InterpolateSmooth; + + } + + }, + + getValueSize: function () { + + return this.values.length / this.times.length; + + }, + + // move all keyframes either forwards or backwards in time + shift: function ( timeOffset ) { + + if ( timeOffset !== 0.0 ) { + + var times = this.times; + + for ( var i = 0, n = times.length; i !== n; ++ i ) { + + times[ i ] += timeOffset; + + } + + } + + return this; + + }, + + // scale all keyframe times by a factor (useful for frame <-> seconds conversions) + scale: function ( timeScale ) { + + if ( timeScale !== 1.0 ) { + + var times = this.times; + + for ( var i = 0, n = times.length; i !== n; ++ i ) { + + times[ i ] *= timeScale; + + } + + } + + return this; + + }, + + // removes keyframes before and after animation without changing any values within the range [startTime, endTime]. + // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values + trim: function ( startTime, endTime ) { + + var times = this.times, + nKeys = times.length, + from = 0, + to = nKeys - 1; + + while ( from !== nKeys && times[ from ] < startTime ) { + + ++ from; + + } + + while ( to !== - 1 && times[ to ] > endTime ) { + + -- to; + + } + + ++ to; // inclusive -> exclusive bound + + if ( from !== 0 || to !== nKeys ) { + + // empty tracks are forbidden, so keep at least one keyframe + if ( from >= to ) to = Math.max( to, 1 ), from = to - 1; + + var stride = this.getValueSize(); + this.times = AnimationUtils.arraySlice( times, from, to ); + this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride ); + + } + + return this; + + }, + + // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable + validate: function () { + + var valid = true; + + var valueSize = this.getValueSize(); + if ( valueSize - Math.floor( valueSize ) !== 0 ) { + + console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this ); + valid = false; + + } + + var times = this.times, + values = this.values, + + nKeys = times.length; + + if ( nKeys === 0 ) { + + console.error( 'THREE.KeyframeTrack: Track is empty.', this ); + valid = false; + + } + + var prevTime = null; + + for ( var i = 0; i !== nKeys; i ++ ) { + + var currTime = times[ i ]; + + if ( typeof currTime === 'number' && isNaN( currTime ) ) { + + console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime ); + valid = false; + break; + + } + + if ( prevTime !== null && prevTime > currTime ) { + + console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime ); + valid = false; + break; + + } + + prevTime = currTime; + + } + + if ( values !== undefined ) { + + if ( AnimationUtils.isTypedArray( values ) ) { + + for ( var i = 0, n = values.length; i !== n; ++ i ) { + + var value = values[ i ]; + + if ( isNaN( value ) ) { + + console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value ); + valid = false; + break; + + } + + } + + } + + } + + return valid; + + }, + + // removes equivalent sequential keys as common in morph target sequences + // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) + optimize: function () { + + var times = this.times, + values = this.values, + stride = this.getValueSize(), + + smoothInterpolation = this.getInterpolation() === InterpolateSmooth, + + writeIndex = 1, + lastIndex = times.length - 1; + + for ( var i = 1; i < lastIndex; ++ i ) { + + var keep = false; + + var time = times[ i ]; + var timeNext = times[ i + 1 ]; + + // remove adjacent keyframes scheduled at the same time + + if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) { + + if ( ! smoothInterpolation ) { + + // remove unnecessary keyframes same as their neighbors + + var offset = i * stride, + offsetP = offset - stride, + offsetN = offset + stride; + + for ( var j = 0; j !== stride; ++ j ) { + + var value = values[ offset + j ]; + + if ( value !== values[ offsetP + j ] || + value !== values[ offsetN + j ] ) { + + keep = true; + break; + + } + + } + + } else { + + keep = true; + + } + + } + + // in-place compaction + + if ( keep ) { + + if ( i !== writeIndex ) { + + times[ writeIndex ] = times[ i ]; + + var readOffset = i * stride, + writeOffset = writeIndex * stride; + + for ( var j = 0; j !== stride; ++ j ) { + + values[ writeOffset + j ] = values[ readOffset + j ]; + + } + + } + + ++ writeIndex; + + } + + } + + // flush last keyframe (compaction looks ahead) + + if ( lastIndex > 0 ) { + + times[ writeIndex ] = times[ lastIndex ]; + + for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) { + + values[ writeOffset + j ] = values[ readOffset + j ]; + + } + + ++ writeIndex; + + } + + if ( writeIndex !== times.length ) { + + this.times = AnimationUtils.arraySlice( times, 0, writeIndex ); + this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride ); + + } + + return this; + + } + + } ); + + /** + * + * A Track of vectored keyframe values. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function VectorKeyframeTrack( name, times, values, interpolation ) { + + KeyframeTrack.call( this, name, times, values, interpolation ); + + } + + VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), { + + constructor: VectorKeyframeTrack, + + ValueTypeName: 'vector' + + // ValueBufferType is inherited + + // DefaultInterpolation is inherited + + } ); + + /** + * + * Reusable set of Tracks that represent an animation. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + */ + + function AnimationClip( name, duration, tracks ) { + + this.name = name; + this.tracks = tracks; + this.duration = ( duration !== undefined ) ? duration : - 1; + + this.uuid = _Math.generateUUID(); + + // this means it should figure out its duration by scanning the tracks + if ( this.duration < 0 ) { + + this.resetDuration(); + + } + + this.optimize(); + + } + + Object.assign( AnimationClip, { + + parse: function ( json ) { + + var tracks = [], + jsonTracks = json.tracks, + frameTime = 1.0 / ( json.fps || 1.0 ); + + for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) { + + tracks.push( KeyframeTrack.parse( jsonTracks[ i ] ).scale( frameTime ) ); + + } + + return new AnimationClip( json.name, json.duration, tracks ); + + }, + + toJSON: function ( clip ) { + + var tracks = [], + clipTracks = clip.tracks; + + var json = { + + 'name': clip.name, + 'duration': clip.duration, + 'tracks': tracks + + }; + + for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) { + + tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) ); + + } + + return json; + + }, + + CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) { + + var numMorphTargets = morphTargetSequence.length; + var tracks = []; + + for ( var i = 0; i < numMorphTargets; i ++ ) { + + var times = []; + var values = []; + + times.push( + ( i + numMorphTargets - 1 ) % numMorphTargets, + i, + ( i + 1 ) % numMorphTargets ); + + values.push( 0, 1, 0 ); + + var order = AnimationUtils.getKeyframeOrder( times ); + times = AnimationUtils.sortedArray( times, 1, order ); + values = AnimationUtils.sortedArray( values, 1, order ); + + // if there is a key at the first frame, duplicate it as the + // last frame as well for perfect loop. + if ( ! noLoop && times[ 0 ] === 0 ) { + + times.push( numMorphTargets ); + values.push( values[ 0 ] ); + + } + + tracks.push( + new NumberKeyframeTrack( + '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']', + times, values + ).scale( 1.0 / fps ) ); + + } + + return new AnimationClip( name, - 1, tracks ); + + }, + + findByName: function ( objectOrClipArray, name ) { + + var clipArray = objectOrClipArray; + + if ( ! Array.isArray( objectOrClipArray ) ) { + + var o = objectOrClipArray; + clipArray = o.geometry && o.geometry.animations || o.animations; + + } + + for ( var i = 0; i < clipArray.length; i ++ ) { + + if ( clipArray[ i ].name === name ) { + + return clipArray[ i ]; + + } + + } + + return null; + + }, + + CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) { + + var animationToMorphTargets = {}; + + // tested with https://regex101.com/ on trick sequences + // such flamingo_flyA_003, flamingo_run1_003, crdeath0059 + var pattern = /^([\w-]*?)([\d]+)$/; + + // sort morph target names into animation groups based + // patterns like Walk_001, Walk_002, Run_001, Run_002 + for ( var i = 0, il = morphTargets.length; i < il; i ++ ) { + + var morphTarget = morphTargets[ i ]; + var parts = morphTarget.name.match( pattern ); + + if ( parts && parts.length > 1 ) { + + var name = parts[ 1 ]; + + var animationMorphTargets = animationToMorphTargets[ name ]; + if ( ! animationMorphTargets ) { + + animationToMorphTargets[ name ] = animationMorphTargets = []; + + } + + animationMorphTargets.push( morphTarget ); + + } + + } + + var clips = []; + + for ( var name in animationToMorphTargets ) { + + clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) ); + + } + + return clips; + + }, + + // parse the animation.hierarchy format + parseAnimation: function ( animation, bones ) { + + if ( ! animation ) { + + console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' ); + return null; + + } + + var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) { + + // only return track if there are actually keys. + if ( animationKeys.length !== 0 ) { + + var times = []; + var values = []; + + AnimationUtils.flattenJSON( animationKeys, times, values, propertyName ); + + // empty keys are filtered out, so check again + if ( times.length !== 0 ) { + + destTracks.push( new trackType( trackName, times, values ) ); + + } + + } + + }; + + var tracks = []; + + var clipName = animation.name || 'default'; + // automatic length determination in AnimationClip. + var duration = animation.length || - 1; + var fps = animation.fps || 30; + + var hierarchyTracks = animation.hierarchy || []; + + for ( var h = 0; h < hierarchyTracks.length; h ++ ) { + + var animationKeys = hierarchyTracks[ h ].keys; + + // skip empty tracks + if ( ! animationKeys || animationKeys.length === 0 ) continue; + + // process morph targets + if ( animationKeys[ 0 ].morphTargets ) { + + // figure out all morph targets used in this track + var morphTargetNames = {}; + + for ( var k = 0; k < animationKeys.length; k ++ ) { + + if ( animationKeys[ k ].morphTargets ) { + + for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) { + + morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1; + + } + + } + + } + + // create a track for each morph target with all zero + // morphTargetInfluences except for the keys in which + // the morphTarget is named. + for ( var morphTargetName in morphTargetNames ) { + + var times = []; + var values = []; + + for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) { + + var animationKey = animationKeys[ k ]; + + times.push( animationKey.time ); + values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 ); + + } + + tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) ); + + } + + duration = morphTargetNames.length * ( fps || 1.0 ); + + } else { + + // ...assume skeletal animation + + var boneName = '.bones[' + bones[ h ].name + ']'; + + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.position', + animationKeys, 'pos', tracks ); + + addNonemptyTrack( + QuaternionKeyframeTrack, boneName + '.quaternion', + animationKeys, 'rot', tracks ); + + addNonemptyTrack( + VectorKeyframeTrack, boneName + '.scale', + animationKeys, 'scl', tracks ); + + } + + } + + if ( tracks.length === 0 ) { + + return null; + + } + + var clip = new AnimationClip( clipName, duration, tracks ); + + return clip; + + } + + } ); + + Object.assign( AnimationClip.prototype, { + + resetDuration: function () { + + var tracks = this.tracks, duration = 0; + + for ( var i = 0, n = tracks.length; i !== n; ++ i ) { + + var track = this.tracks[ i ]; + + duration = Math.max( duration, track.times[ track.times.length - 1 ] ); + + } + + this.duration = duration; + + }, + + trim: function () { + + for ( var i = 0; i < this.tracks.length; i ++ ) { + + this.tracks[ i ].trim( 0, this.duration ); + + } + + return this; + + }, + + optimize: function () { + + for ( var i = 0; i < this.tracks.length; i ++ ) { + + this.tracks[ i ].optimize(); + + } + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function MaterialLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + this.textures = {}; + + } + + Object.assign( MaterialLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new FileLoader( scope.manager ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + setTextures: function ( value ) { + + this.textures = value; + + }, + + parse: function ( json ) { + + var textures = this.textures; + + function getTexture( name ) { + + if ( textures[ name ] === undefined ) { + + console.warn( 'THREE.MaterialLoader: Undefined texture', name ); + + } + + return textures[ name ]; + + } + + var material = new Materials[ json.type ](); + + if ( json.uuid !== undefined ) material.uuid = json.uuid; + if ( json.name !== undefined ) material.name = json.name; + if ( json.color !== undefined ) material.color.setHex( json.color ); + if ( json.roughness !== undefined ) material.roughness = json.roughness; + if ( json.metalness !== undefined ) material.metalness = json.metalness; + if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive ); + if ( json.specular !== undefined ) material.specular.setHex( json.specular ); + if ( json.shininess !== undefined ) material.shininess = json.shininess; + if ( json.clearCoat !== undefined ) material.clearCoat = json.clearCoat; + if ( json.clearCoatRoughness !== undefined ) material.clearCoatRoughness = json.clearCoatRoughness; + if ( json.uniforms !== undefined ) material.uniforms = json.uniforms; + if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader; + if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader; + if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors; + if ( json.fog !== undefined ) material.fog = json.fog; + if ( json.flatShading !== undefined ) material.flatShading = json.flatShading; + if ( json.blending !== undefined ) material.blending = json.blending; + if ( json.side !== undefined ) material.side = json.side; + if ( json.opacity !== undefined ) material.opacity = json.opacity; + if ( json.transparent !== undefined ) material.transparent = json.transparent; + if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest; + if ( json.depthTest !== undefined ) material.depthTest = json.depthTest; + if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite; + if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite; + if ( json.wireframe !== undefined ) material.wireframe = json.wireframe; + if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth; + if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap; + if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin; + + if ( json.rotation !== undefined ) material.rotation = json.rotation; + + if ( json.linewidth !== 1 ) material.linewidth = json.linewidth; + if ( json.dashSize !== undefined ) material.dashSize = json.dashSize; + if ( json.gapSize !== undefined ) material.gapSize = json.gapSize; + if ( json.scale !== undefined ) material.scale = json.scale; + + if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset; + if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor; + if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits; + + if ( json.skinning !== undefined ) material.skinning = json.skinning; + if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets; + if ( json.dithering !== undefined ) material.dithering = json.dithering; + + if ( json.visible !== undefined ) material.visible = json.visible; + if ( json.userData !== undefined ) material.userData = json.userData; + + // Deprecated + + if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading + + // for PointsMaterial + + if ( json.size !== undefined ) material.size = json.size; + if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation; + + // maps + + if ( json.map !== undefined ) material.map = getTexture( json.map ); + + if ( json.alphaMap !== undefined ) { + + material.alphaMap = getTexture( json.alphaMap ); + material.transparent = true; + + } + + if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap ); + if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale; + + if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap ); + if ( json.normalScale !== undefined ) { + + var normalScale = json.normalScale; + + if ( Array.isArray( normalScale ) === false ) { + + // Blender exporter used to export a scalar. See #7459 + + normalScale = [ normalScale, normalScale ]; + + } + + material.normalScale = new Vector2().fromArray( normalScale ); + + } + + if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap ); + if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale; + if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias; + + if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap ); + if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap ); + + if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap ); + if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity; + + if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap ); + + if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap ); + + if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity; + + if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap ); + if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity; + + if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap ); + if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity; + + if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap ); + + return material; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function BufferGeometryLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( BufferGeometryLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new FileLoader( scope.manager ); + loader.load( url, function ( text ) { + + onLoad( scope.parse( JSON.parse( text ) ) ); + + }, onProgress, onError ); + + }, + + parse: function ( json ) { + + var geometry = new BufferGeometry(); + + var index = json.data.index; + + if ( index !== undefined ) { + + var typedArray = new TYPED_ARRAYS[ index.type ]( index.array ); + geometry.setIndex( new BufferAttribute( typedArray, 1 ) ); + + } + + var attributes = json.data.attributes; + + for ( var key in attributes ) { + + var attribute = attributes[ key ]; + var typedArray = new TYPED_ARRAYS[ attribute.type ]( attribute.array ); + + geometry.addAttribute( key, new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized ) ); + + } + + var groups = json.data.groups || json.data.drawcalls || json.data.offsets; + + if ( groups !== undefined ) { + + for ( var i = 0, n = groups.length; i !== n; ++ i ) { + + var group = groups[ i ]; + + geometry.addGroup( group.start, group.count, group.materialIndex ); + + } + + } + + var boundingSphere = json.data.boundingSphere; + + if ( boundingSphere !== undefined ) { + + var center = new Vector3(); + + if ( boundingSphere.center !== undefined ) { + + center.fromArray( boundingSphere.center ); + + } + + geometry.boundingSphere = new Sphere( center, boundingSphere.radius ); + + } + + return geometry; + + } + + } ); + + var TYPED_ARRAYS = { + Int8Array: Int8Array, + Uint8Array: Uint8Array, + // Workaround for IE11 pre KB2929437. See #11440 + Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array, + Int16Array: Int16Array, + Uint16Array: Uint16Array, + Int32Array: Int32Array, + Uint32Array: Uint32Array, + Float32Array: Float32Array, + Float64Array: Float64Array + }; + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function Loader() { + + this.onLoadStart = function () {}; + this.onLoadProgress = function () {}; + this.onLoadComplete = function () {}; + + } + + Loader.Handlers = { + + handlers: [], + + add: function ( regex, loader ) { + + this.handlers.push( regex, loader ); + + }, + + get: function ( file ) { + + var handlers = this.handlers; + + for ( var i = 0, l = handlers.length; i < l; i += 2 ) { + + var regex = handlers[ i ]; + var loader = handlers[ i + 1 ]; + + if ( regex.test( file ) ) { + + return loader; + + } + + } + + return null; + + } + + }; + + Object.assign( Loader.prototype, { + + crossOrigin: undefined, + + initMaterials: function ( materials, texturePath, crossOrigin ) { + + var array = []; + + for ( var i = 0; i < materials.length; ++ i ) { + + array[ i ] = this.createMaterial( materials[ i ], texturePath, crossOrigin ); + + } + + return array; + + }, + + createMaterial: ( function () { + + var BlendingMode = { + NoBlending: NoBlending, + NormalBlending: NormalBlending, + AdditiveBlending: AdditiveBlending, + SubtractiveBlending: SubtractiveBlending, + MultiplyBlending: MultiplyBlending, + CustomBlending: CustomBlending + }; + + var color = new Color(); + var textureLoader = new TextureLoader(); + var materialLoader = new MaterialLoader(); + + return function createMaterial( m, texturePath, crossOrigin ) { + + // convert from old material format + + var textures = {}; + + function loadTexture( path, repeat, offset, wrap, anisotropy ) { + + var fullPath = texturePath + path; + var loader = Loader.Handlers.get( fullPath ); + + var texture; + + if ( loader !== null ) { + + texture = loader.load( fullPath ); + + } else { + + textureLoader.setCrossOrigin( crossOrigin ); + texture = textureLoader.load( fullPath ); + + } + + if ( repeat !== undefined ) { + + texture.repeat.fromArray( repeat ); + + if ( repeat[ 0 ] !== 1 ) texture.wrapS = RepeatWrapping; + if ( repeat[ 1 ] !== 1 ) texture.wrapT = RepeatWrapping; + + } + + if ( offset !== undefined ) { + + texture.offset.fromArray( offset ); + + } + + if ( wrap !== undefined ) { + + if ( wrap[ 0 ] === 'repeat' ) texture.wrapS = RepeatWrapping; + if ( wrap[ 0 ] === 'mirror' ) texture.wrapS = MirroredRepeatWrapping; + + if ( wrap[ 1 ] === 'repeat' ) texture.wrapT = RepeatWrapping; + if ( wrap[ 1 ] === 'mirror' ) texture.wrapT = MirroredRepeatWrapping; + + } + + if ( anisotropy !== undefined ) { + + texture.anisotropy = anisotropy; + + } + + var uuid = _Math.generateUUID(); + + textures[ uuid ] = texture; + + return uuid; + + } + + // + + var json = { + uuid: _Math.generateUUID(), + type: 'MeshLambertMaterial' + }; + + for ( var name in m ) { + + var value = m[ name ]; + + switch ( name ) { + + case 'DbgColor': + case 'DbgIndex': + case 'opticalDensity': + case 'illumination': + break; + case 'DbgName': + json.name = value; + break; + case 'blending': + json.blending = BlendingMode[ value ]; + break; + case 'colorAmbient': + case 'mapAmbient': + console.warn( 'THREE.Loader.createMaterial:', name, 'is no longer supported.' ); + break; + case 'colorDiffuse': + json.color = color.fromArray( value ).getHex(); + break; + case 'colorSpecular': + json.specular = color.fromArray( value ).getHex(); + break; + case 'colorEmissive': + json.emissive = color.fromArray( value ).getHex(); + break; + case 'specularCoef': + json.shininess = value; + break; + case 'shading': + if ( value.toLowerCase() === 'basic' ) json.type = 'MeshBasicMaterial'; + if ( value.toLowerCase() === 'phong' ) json.type = 'MeshPhongMaterial'; + if ( value.toLowerCase() === 'standard' ) json.type = 'MeshStandardMaterial'; + break; + case 'mapDiffuse': + json.map = loadTexture( value, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy ); + break; + case 'mapDiffuseRepeat': + case 'mapDiffuseOffset': + case 'mapDiffuseWrap': + case 'mapDiffuseAnisotropy': + break; + case 'mapEmissive': + json.emissiveMap = loadTexture( value, m.mapEmissiveRepeat, m.mapEmissiveOffset, m.mapEmissiveWrap, m.mapEmissiveAnisotropy ); + break; + case 'mapEmissiveRepeat': + case 'mapEmissiveOffset': + case 'mapEmissiveWrap': + case 'mapEmissiveAnisotropy': + break; + case 'mapLight': + json.lightMap = loadTexture( value, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy ); + break; + case 'mapLightRepeat': + case 'mapLightOffset': + case 'mapLightWrap': + case 'mapLightAnisotropy': + break; + case 'mapAO': + json.aoMap = loadTexture( value, m.mapAORepeat, m.mapAOOffset, m.mapAOWrap, m.mapAOAnisotropy ); + break; + case 'mapAORepeat': + case 'mapAOOffset': + case 'mapAOWrap': + case 'mapAOAnisotropy': + break; + case 'mapBump': + json.bumpMap = loadTexture( value, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy ); + break; + case 'mapBumpScale': + json.bumpScale = value; + break; + case 'mapBumpRepeat': + case 'mapBumpOffset': + case 'mapBumpWrap': + case 'mapBumpAnisotropy': + break; + case 'mapNormal': + json.normalMap = loadTexture( value, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy ); + break; + case 'mapNormalFactor': + json.normalScale = [ value, value ]; + break; + case 'mapNormalRepeat': + case 'mapNormalOffset': + case 'mapNormalWrap': + case 'mapNormalAnisotropy': + break; + case 'mapSpecular': + json.specularMap = loadTexture( value, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy ); + break; + case 'mapSpecularRepeat': + case 'mapSpecularOffset': + case 'mapSpecularWrap': + case 'mapSpecularAnisotropy': + break; + case 'mapMetalness': + json.metalnessMap = loadTexture( value, m.mapMetalnessRepeat, m.mapMetalnessOffset, m.mapMetalnessWrap, m.mapMetalnessAnisotropy ); + break; + case 'mapMetalnessRepeat': + case 'mapMetalnessOffset': + case 'mapMetalnessWrap': + case 'mapMetalnessAnisotropy': + break; + case 'mapRoughness': + json.roughnessMap = loadTexture( value, m.mapRoughnessRepeat, m.mapRoughnessOffset, m.mapRoughnessWrap, m.mapRoughnessAnisotropy ); + break; + case 'mapRoughnessRepeat': + case 'mapRoughnessOffset': + case 'mapRoughnessWrap': + case 'mapRoughnessAnisotropy': + break; + case 'mapAlpha': + json.alphaMap = loadTexture( value, m.mapAlphaRepeat, m.mapAlphaOffset, m.mapAlphaWrap, m.mapAlphaAnisotropy ); + break; + case 'mapAlphaRepeat': + case 'mapAlphaOffset': + case 'mapAlphaWrap': + case 'mapAlphaAnisotropy': + break; + case 'flipSided': + json.side = BackSide; + break; + case 'doubleSided': + json.side = DoubleSide; + break; + case 'transparency': + console.warn( 'THREE.Loader.createMaterial: transparency has been renamed to opacity' ); + json.opacity = value; + break; + case 'depthTest': + case 'depthWrite': + case 'colorWrite': + case 'opacity': + case 'reflectivity': + case 'transparent': + case 'visible': + case 'wireframe': + json[ name ] = value; + break; + case 'vertexColors': + if ( value === true ) json.vertexColors = VertexColors; + if ( value === 'face' ) json.vertexColors = FaceColors; + break; + default: + console.error( 'THREE.Loader.createMaterial: Unsupported', name, value ); + break; + + } + + } + + if ( json.type === 'MeshBasicMaterial' ) delete json.emissive; + if ( json.type !== 'MeshPhongMaterial' ) delete json.specular; + + if ( json.opacity < 1 ) json.transparent = true; + + materialLoader.setTextures( textures ); + + return materialLoader.parse( json ); + + }; + + } )() + + } ); + + /** + * @author Don McCurdy / https://www.donmccurdy.com + */ + + var LoaderUtils = { + + decodeText: function ( array ) { + + if ( typeof TextDecoder !== 'undefined' ) { + + return new TextDecoder().decode( array ); + + } + + // Avoid the String.fromCharCode.apply(null, array) shortcut, which + // throws a "maximum call stack size exceeded" error for large arrays. + + var s = ''; + + for ( var i = 0, il = array.length; i < il; i ++ ) { + + // Implicitly assumes little-endian. + s += String.fromCharCode( array[ i ] ); + + } + + // Merges multi-byte utf-8 characters. + return decodeURIComponent( escape( s ) ); + + }, + + extractUrlBase: function ( url ) { + + var parts = url.split( '/' ); + + if ( parts.length === 1 ) return './'; + + parts.pop(); + + return parts.join( '/' ) + '/'; + + } + + }; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author alteredq / http://alteredqualia.com/ + */ + + function JSONLoader( manager ) { + + if ( typeof manager === 'boolean' ) { + + console.warn( 'THREE.JSONLoader: showStatus parameter has been removed from constructor.' ); + manager = undefined; + + } + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + this.withCredentials = false; + + } + + Object.assign( JSONLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var texturePath = this.texturePath && ( typeof this.texturePath === 'string' ) ? this.texturePath : LoaderUtils.extractUrlBase( url ); + + var loader = new FileLoader( this.manager ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + var json = JSON.parse( text ); + var metadata = json.metadata; + + if ( metadata !== undefined ) { + + var type = metadata.type; + + if ( type !== undefined ) { + + if ( type.toLowerCase() === 'object' ) { + + console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.ObjectLoader instead.' ); + return; + + } + + if ( type.toLowerCase() === 'scene' ) { + + console.error( 'THREE.JSONLoader: ' + url + ' should be loaded with THREE.SceneLoader instead.' ); + return; + + } + + } + + } + + var object = scope.parse( json, texturePath ); + onLoad( object.geometry, object.materials ); + + }, onProgress, onError ); + + }, + + setTexturePath: function ( value ) { + + this.texturePath = value; + + }, + + parse: ( function () { + + function parseModel( json, geometry ) { + + function isBitSet( value, position ) { + + return value & ( 1 << position ); + + } + + var i, j, fi, + + offset, zLength, + + colorIndex, normalIndex, uvIndex, materialIndex, + + type, + isQuad, + hasMaterial, + hasFaceVertexUv, + hasFaceNormal, hasFaceVertexNormal, + hasFaceColor, hasFaceVertexColor, + + vertex, face, faceA, faceB, hex, normal, + + uvLayer, uv, u, v, + + faces = json.faces, + vertices = json.vertices, + normals = json.normals, + colors = json.colors, + + scale = json.scale, + + nUvLayers = 0; + + + if ( json.uvs !== undefined ) { + + // disregard empty arrays + + for ( i = 0; i < json.uvs.length; i ++ ) { + + if ( json.uvs[ i ].length ) nUvLayers ++; + + } + + for ( i = 0; i < nUvLayers; i ++ ) { + + geometry.faceVertexUvs[ i ] = []; + + } + + } + + offset = 0; + zLength = vertices.length; + + while ( offset < zLength ) { + + vertex = new Vector3(); + + vertex.x = vertices[ offset ++ ] * scale; + vertex.y = vertices[ offset ++ ] * scale; + vertex.z = vertices[ offset ++ ] * scale; + + geometry.vertices.push( vertex ); + + } + + offset = 0; + zLength = faces.length; + + while ( offset < zLength ) { + + type = faces[ offset ++ ]; + + isQuad = isBitSet( type, 0 ); + hasMaterial = isBitSet( type, 1 ); + hasFaceVertexUv = isBitSet( type, 3 ); + hasFaceNormal = isBitSet( type, 4 ); + hasFaceVertexNormal = isBitSet( type, 5 ); + hasFaceColor = isBitSet( type, 6 ); + hasFaceVertexColor = isBitSet( type, 7 ); + + // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor); + + if ( isQuad ) { + + faceA = new Face3(); + faceA.a = faces[ offset ]; + faceA.b = faces[ offset + 1 ]; + faceA.c = faces[ offset + 3 ]; + + faceB = new Face3(); + faceB.a = faces[ offset + 1 ]; + faceB.b = faces[ offset + 2 ]; + faceB.c = faces[ offset + 3 ]; + + offset += 4; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + faceA.materialIndex = materialIndex; + faceB.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i ++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + geometry.faceVertexUvs[ i ][ fi + 1 ] = []; + + for ( j = 0; j < 4; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new Vector2( u, v ); + + if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv ); + if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + faceA.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + faceB.normal.copy( faceA.normal ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 4; i ++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + + if ( i !== 2 ) faceA.vertexNormals.push( normal ); + if ( i !== 0 ) faceB.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + faceA.color.setHex( hex ); + faceB.color.setHex( hex ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 4; i ++ ) { + + colorIndex = faces[ offset ++ ]; + hex = colors[ colorIndex ]; + + if ( i !== 2 ) faceA.vertexColors.push( new Color( hex ) ); + if ( i !== 0 ) faceB.vertexColors.push( new Color( hex ) ); + + } + + } + + geometry.faces.push( faceA ); + geometry.faces.push( faceB ); + + } else { + + face = new Face3(); + face.a = faces[ offset ++ ]; + face.b = faces[ offset ++ ]; + face.c = faces[ offset ++ ]; + + if ( hasMaterial ) { + + materialIndex = faces[ offset ++ ]; + face.materialIndex = materialIndex; + + } + + // to get face <=> uv index correspondence + + fi = geometry.faces.length; + + if ( hasFaceVertexUv ) { + + for ( i = 0; i < nUvLayers; i ++ ) { + + uvLayer = json.uvs[ i ]; + + geometry.faceVertexUvs[ i ][ fi ] = []; + + for ( j = 0; j < 3; j ++ ) { + + uvIndex = faces[ offset ++ ]; + + u = uvLayer[ uvIndex * 2 ]; + v = uvLayer[ uvIndex * 2 + 1 ]; + + uv = new Vector2( u, v ); + + geometry.faceVertexUvs[ i ][ fi ].push( uv ); + + } + + } + + } + + if ( hasFaceNormal ) { + + normalIndex = faces[ offset ++ ] * 3; + + face.normal.set( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + } + + if ( hasFaceVertexNormal ) { + + for ( i = 0; i < 3; i ++ ) { + + normalIndex = faces[ offset ++ ] * 3; + + normal = new Vector3( + normals[ normalIndex ++ ], + normals[ normalIndex ++ ], + normals[ normalIndex ] + ); + + face.vertexNormals.push( normal ); + + } + + } + + + if ( hasFaceColor ) { + + colorIndex = faces[ offset ++ ]; + face.color.setHex( colors[ colorIndex ] ); + + } + + + if ( hasFaceVertexColor ) { + + for ( i = 0; i < 3; i ++ ) { + + colorIndex = faces[ offset ++ ]; + face.vertexColors.push( new Color( colors[ colorIndex ] ) ); + + } + + } + + geometry.faces.push( face ); + + } + + } + + } + + function parseSkin( json, geometry ) { + + var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2; + + if ( json.skinWeights ) { + + for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) { + + var x = json.skinWeights[ i ]; + var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0; + var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0; + var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0; + + geometry.skinWeights.push( new Vector4( x, y, z, w ) ); + + } + + } + + if ( json.skinIndices ) { + + for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) { + + var a = json.skinIndices[ i ]; + var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0; + var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0; + var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0; + + geometry.skinIndices.push( new Vector4( a, b, c, d ) ); + + } + + } + + geometry.bones = json.bones; + + if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) { + + console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' + + geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' ); + + } + + } + + function parseMorphing( json, geometry ) { + + var scale = json.scale; + + if ( json.morphTargets !== undefined ) { + + for ( var i = 0, l = json.morphTargets.length; i < l; i ++ ) { + + geometry.morphTargets[ i ] = {}; + geometry.morphTargets[ i ].name = json.morphTargets[ i ].name; + geometry.morphTargets[ i ].vertices = []; + + var dstVertices = geometry.morphTargets[ i ].vertices; + var srcVertices = json.morphTargets[ i ].vertices; + + for ( var v = 0, vl = srcVertices.length; v < vl; v += 3 ) { + + var vertex = new Vector3(); + vertex.x = srcVertices[ v ] * scale; + vertex.y = srcVertices[ v + 1 ] * scale; + vertex.z = srcVertices[ v + 2 ] * scale; + + dstVertices.push( vertex ); + + } + + } + + } + + if ( json.morphColors !== undefined && json.morphColors.length > 0 ) { + + console.warn( 'THREE.JSONLoader: "morphColors" no longer supported. Using them as face colors.' ); + + var faces = geometry.faces; + var morphColors = json.morphColors[ 0 ].colors; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + faces[ i ].color.fromArray( morphColors, i * 3 ); + + } + + } + + } + + function parseAnimations( json, geometry ) { + + var outputAnimations = []; + + // parse old style Bone/Hierarchy animations + var animations = []; + + if ( json.animation !== undefined ) { + + animations.push( json.animation ); + + } + + if ( json.animations !== undefined ) { + + if ( json.animations.length ) { + + animations = animations.concat( json.animations ); + + } else { + + animations.push( json.animations ); + + } + + } + + for ( var i = 0; i < animations.length; i ++ ) { + + var clip = AnimationClip.parseAnimation( animations[ i ], geometry.bones ); + if ( clip ) outputAnimations.push( clip ); + + } + + // parse implicit morph animations + if ( geometry.morphTargets ) { + + // TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary. + var morphAnimationClips = AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 ); + outputAnimations = outputAnimations.concat( morphAnimationClips ); + + } + + if ( outputAnimations.length > 0 ) geometry.animations = outputAnimations; + + } + + return function parse( json, texturePath ) { + + if ( json.data !== undefined ) { + + // Geometry 4.0 spec + json = json.data; + + } + + if ( json.scale !== undefined ) { + + json.scale = 1.0 / json.scale; + + } else { + + json.scale = 1.0; + + } + + var geometry = new Geometry(); + + parseModel( json, geometry ); + parseSkin( json, geometry ); + parseMorphing( json, geometry ); + parseAnimations( json, geometry ); + + geometry.computeFaceNormals(); + geometry.computeBoundingSphere(); + + if ( json.materials === undefined || json.materials.length === 0 ) { + + return { geometry: geometry }; + + } else { + + var materials = Loader.prototype.initMaterials( json.materials, texturePath, this.crossOrigin ); + + return { geometry: geometry, materials: materials }; + + } + + }; + + } )() + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function ObjectLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + this.texturePath = ''; + + } + + Object.assign( ObjectLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + if ( this.texturePath === '' ) { + + this.texturePath = url.substring( 0, url.lastIndexOf( '/' ) + 1 ); + + } + + var scope = this; + + var loader = new FileLoader( scope.manager ); + loader.load( url, function ( text ) { + + var json = null; + + try { + + json = JSON.parse( text ); + + } catch ( error ) { + + if ( onError !== undefined ) onError( error ); + + console.error( 'THREE:ObjectLoader: Can\'t parse ' + url + '.', error.message ); + + return; + + } + + var metadata = json.metadata; + + if ( metadata === undefined || metadata.type === undefined || metadata.type.toLowerCase() === 'geometry' ) { + + console.error( 'THREE.ObjectLoader: Can\'t load ' + url + '. Use THREE.JSONLoader instead.' ); + return; + + } + + scope.parse( json, onLoad ); + + }, onProgress, onError ); + + }, + + setTexturePath: function ( value ) { + + this.texturePath = value; + + }, + + setCrossOrigin: function ( value ) { + + this.crossOrigin = value; + + }, + + parse: function ( json, onLoad ) { + + var shapes = this.parseShape( json.shapes ); + var geometries = this.parseGeometries( json.geometries, shapes ); + + var images = this.parseImages( json.images, function () { + + if ( onLoad !== undefined ) onLoad( object ); + + } ); + + var textures = this.parseTextures( json.textures, images ); + var materials = this.parseMaterials( json.materials, textures ); + + var object = this.parseObject( json.object, geometries, materials ); + + if ( json.animations ) { + + object.animations = this.parseAnimations( json.animations ); + + } + + if ( json.images === undefined || json.images.length === 0 ) { + + if ( onLoad !== undefined ) onLoad( object ); + + } + + return object; + + }, + + parseShape: function ( json ) { + + var shapes = {}; + + if ( json !== undefined ) { + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var shape = new Shape().fromJSON( json[ i ] ); + + shapes[ shape.uuid ] = shape; + + } + + } + + return shapes; + + }, + + parseGeometries: function ( json, shapes ) { + + var geometries = {}; + + if ( json !== undefined ) { + + var geometryLoader = new JSONLoader(); + var bufferGeometryLoader = new BufferGeometryLoader(); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var geometry; + var data = json[ i ]; + + switch ( data.type ) { + + case 'PlaneGeometry': + case 'PlaneBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.width, + data.height, + data.widthSegments, + data.heightSegments + ); + + break; + + case 'BoxGeometry': + case 'BoxBufferGeometry': + case 'CubeGeometry': // backwards compatible + + geometry = new Geometries[ data.type ]( + data.width, + data.height, + data.depth, + data.widthSegments, + data.heightSegments, + data.depthSegments + ); + + break; + + case 'CircleGeometry': + case 'CircleBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.segments, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'CylinderGeometry': + case 'CylinderBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radiusTop, + data.radiusBottom, + data.height, + data.radialSegments, + data.heightSegments, + data.openEnded, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'ConeGeometry': + case 'ConeBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.height, + data.radialSegments, + data.heightSegments, + data.openEnded, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'SphereGeometry': + case 'SphereBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.widthSegments, + data.heightSegments, + data.phiStart, + data.phiLength, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'DodecahedronGeometry': + case 'DodecahedronBufferGeometry': + case 'IcosahedronGeometry': + case 'IcosahedronBufferGeometry': + case 'OctahedronGeometry': + case 'OctahedronBufferGeometry': + case 'TetrahedronGeometry': + case 'TetrahedronBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.detail + ); + + break; + + case 'RingGeometry': + case 'RingBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.innerRadius, + data.outerRadius, + data.thetaSegments, + data.phiSegments, + data.thetaStart, + data.thetaLength + ); + + break; + + case 'TorusGeometry': + case 'TorusBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.tube, + data.radialSegments, + data.tubularSegments, + data.arc + ); + + break; + + case 'TorusKnotGeometry': + case 'TorusKnotBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.radius, + data.tube, + data.tubularSegments, + data.radialSegments, + data.p, + data.q + ); + + break; + + case 'LatheGeometry': + case 'LatheBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.points, + data.segments, + data.phiStart, + data.phiLength + ); + + break; + + case 'PolyhedronGeometry': + case 'PolyhedronBufferGeometry': + + geometry = new Geometries[ data.type ]( + data.vertices, + data.indices, + data.radius, + data.details + ); + + break; + + case 'ShapeGeometry': + case 'ShapeBufferGeometry': + + var geometryShapes = []; + + for ( var i = 0, l = data.shapes.length; i < l; i ++ ) { + + var shape = shapes[ data.shapes[ i ] ]; + + geometryShapes.push( shape ); + + } + + geometry = new Geometries[ data.type ]( + geometryShapes, + data.curveSegments + ); + + break; + + case 'BufferGeometry': + + geometry = bufferGeometryLoader.parse( data ); + + break; + + case 'Geometry': + + geometry = geometryLoader.parse( data, this.texturePath ).geometry; + + break; + + default: + + console.warn( 'THREE.ObjectLoader: Unsupported geometry type "' + data.type + '"' ); + + continue; + + } + + geometry.uuid = data.uuid; + + if ( data.name !== undefined ) geometry.name = data.name; + + geometries[ data.uuid ] = geometry; + + } + + } + + return geometries; + + }, + + parseMaterials: function ( json, textures ) { + + var materials = {}; + + if ( json !== undefined ) { + + var loader = new MaterialLoader(); + loader.setTextures( textures ); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var data = json[ i ]; + + if ( data.type === 'MultiMaterial' ) { + + // Deprecated + + var array = []; + + for ( var j = 0; j < data.materials.length; j ++ ) { + + array.push( loader.parse( data.materials[ j ] ) ); + + } + + materials[ data.uuid ] = array; + + } else { + + materials[ data.uuid ] = loader.parse( data ); + + } + + } + + } + + return materials; + + }, + + parseAnimations: function ( json ) { + + var animations = []; + + for ( var i = 0; i < json.length; i ++ ) { + + var clip = AnimationClip.parse( json[ i ] ); + + animations.push( clip ); + + } + + return animations; + + }, + + parseImages: function ( json, onLoad ) { + + var scope = this; + var images = {}; + + function loadImage( url ) { + + scope.manager.itemStart( url ); + + return loader.load( url, function () { + + scope.manager.itemEnd( url ); + + }, undefined, function () { + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + } ); + + } + + if ( json !== undefined && json.length > 0 ) { + + var manager = new LoadingManager( onLoad ); + + var loader = new ImageLoader( manager ); + loader.setCrossOrigin( this.crossOrigin ); + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var image = json[ i ]; + var path = /^(\/\/)|([a-z]+:(\/\/)?)/i.test( image.url ) ? image.url : scope.texturePath + image.url; + + images[ image.uuid ] = loadImage( path ); + + } + + } + + return images; + + }, + + parseTextures: function ( json, images ) { + + function parseConstant( value, type ) { + + if ( typeof value === 'number' ) return value; + + console.warn( 'THREE.ObjectLoader.parseTexture: Constant should be in numeric form.', value ); + + return type[ value ]; + + } + + var textures = {}; + + if ( json !== undefined ) { + + for ( var i = 0, l = json.length; i < l; i ++ ) { + + var data = json[ i ]; + + if ( data.image === undefined ) { + + console.warn( 'THREE.ObjectLoader: No "image" specified for', data.uuid ); + + } + + if ( images[ data.image ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined image', data.image ); + + } + + var texture = new Texture( images[ data.image ] ); + texture.needsUpdate = true; + + texture.uuid = data.uuid; + + if ( data.name !== undefined ) texture.name = data.name; + + if ( data.mapping !== undefined ) texture.mapping = parseConstant( data.mapping, TEXTURE_MAPPING ); + + if ( data.offset !== undefined ) texture.offset.fromArray( data.offset ); + if ( data.repeat !== undefined ) texture.repeat.fromArray( data.repeat ); + if ( data.center !== undefined ) texture.center.fromArray( data.center ); + if ( data.rotation !== undefined ) texture.rotation = data.rotation; + + if ( data.wrap !== undefined ) { + + texture.wrapS = parseConstant( data.wrap[ 0 ], TEXTURE_WRAPPING ); + texture.wrapT = parseConstant( data.wrap[ 1 ], TEXTURE_WRAPPING ); + + } + + if ( data.minFilter !== undefined ) texture.minFilter = parseConstant( data.minFilter, TEXTURE_FILTER ); + if ( data.magFilter !== undefined ) texture.magFilter = parseConstant( data.magFilter, TEXTURE_FILTER ); + if ( data.anisotropy !== undefined ) texture.anisotropy = data.anisotropy; + + if ( data.flipY !== undefined ) texture.flipY = data.flipY; + + textures[ data.uuid ] = texture; + + } + + } + + return textures; + + }, + + parseObject: function ( data, geometries, materials ) { + + var object; + + function getGeometry( name ) { + + if ( geometries[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined geometry', name ); + + } + + return geometries[ name ]; + + } + + function getMaterial( name ) { + + if ( name === undefined ) return undefined; + + if ( Array.isArray( name ) ) { + + var array = []; + + for ( var i = 0, l = name.length; i < l; i ++ ) { + + var uuid = name[ i ]; + + if ( materials[ uuid ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined material', uuid ); + + } + + array.push( materials[ uuid ] ); + + } + + return array; + + } + + if ( materials[ name ] === undefined ) { + + console.warn( 'THREE.ObjectLoader: Undefined material', name ); + + } + + return materials[ name ]; + + } + + switch ( data.type ) { + + case 'Scene': + + object = new Scene(); + + if ( data.background !== undefined ) { + + if ( Number.isInteger( data.background ) ) { + + object.background = new Color( data.background ); + + } + + } + + if ( data.fog !== undefined ) { + + if ( data.fog.type === 'Fog' ) { + + object.fog = new Fog( data.fog.color, data.fog.near, data.fog.far ); + + } else if ( data.fog.type === 'FogExp2' ) { + + object.fog = new FogExp2( data.fog.color, data.fog.density ); + + } + + } + + break; + + case 'PerspectiveCamera': + + object = new PerspectiveCamera( data.fov, data.aspect, data.near, data.far ); + + if ( data.focus !== undefined ) object.focus = data.focus; + if ( data.zoom !== undefined ) object.zoom = data.zoom; + if ( data.filmGauge !== undefined ) object.filmGauge = data.filmGauge; + if ( data.filmOffset !== undefined ) object.filmOffset = data.filmOffset; + if ( data.view !== undefined ) object.view = Object.assign( {}, data.view ); + + break; + + case 'OrthographicCamera': + + object = new OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far ); + + break; + + case 'AmbientLight': + + object = new AmbientLight( data.color, data.intensity ); + + break; + + case 'DirectionalLight': + + object = new DirectionalLight( data.color, data.intensity ); + + break; + + case 'PointLight': + + object = new PointLight( data.color, data.intensity, data.distance, data.decay ); + + break; + + case 'RectAreaLight': + + object = new RectAreaLight( data.color, data.intensity, data.width, data.height ); + + break; + + case 'SpotLight': + + object = new SpotLight( data.color, data.intensity, data.distance, data.angle, data.penumbra, data.decay ); + + break; + + case 'HemisphereLight': + + object = new HemisphereLight( data.color, data.groundColor, data.intensity ); + + break; + + case 'SkinnedMesh': + + console.warn( 'THREE.ObjectLoader.parseObject() does not support SkinnedMesh yet.' ); + + case 'Mesh': + + var geometry = getGeometry( data.geometry ); + var material = getMaterial( data.material ); + + if ( geometry.bones && geometry.bones.length > 0 ) { + + object = new SkinnedMesh( geometry, material ); + + } else { + + object = new Mesh( geometry, material ); + + } + + break; + + case 'LOD': + + object = new LOD(); + + break; + + case 'Line': + + object = new Line( getGeometry( data.geometry ), getMaterial( data.material ), data.mode ); + + break; + + case 'LineLoop': + + object = new LineLoop( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'LineSegments': + + object = new LineSegments( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'PointCloud': + case 'Points': + + object = new Points( getGeometry( data.geometry ), getMaterial( data.material ) ); + + break; + + case 'Sprite': + + object = new Sprite( getMaterial( data.material ) ); + + break; + + case 'Group': + + object = new Group(); + + break; + + default: + + object = new Object3D(); + + } + + object.uuid = data.uuid; + + if ( data.name !== undefined ) object.name = data.name; + if ( data.matrix !== undefined ) { + + object.matrix.fromArray( data.matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + } else { + + if ( data.position !== undefined ) object.position.fromArray( data.position ); + if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation ); + if ( data.quaternion !== undefined ) object.quaternion.fromArray( data.quaternion ); + if ( data.scale !== undefined ) object.scale.fromArray( data.scale ); + + } + + if ( data.castShadow !== undefined ) object.castShadow = data.castShadow; + if ( data.receiveShadow !== undefined ) object.receiveShadow = data.receiveShadow; + + if ( data.shadow ) { + + if ( data.shadow.bias !== undefined ) object.shadow.bias = data.shadow.bias; + if ( data.shadow.radius !== undefined ) object.shadow.radius = data.shadow.radius; + if ( data.shadow.mapSize !== undefined ) object.shadow.mapSize.fromArray( data.shadow.mapSize ); + if ( data.shadow.camera !== undefined ) object.shadow.camera = this.parseObject( data.shadow.camera ); + + } + + if ( data.visible !== undefined ) object.visible = data.visible; + if ( data.userData !== undefined ) object.userData = data.userData; + + if ( data.children !== undefined ) { + + var children = data.children; + + for ( var i = 0; i < children.length; i ++ ) { + + object.add( this.parseObject( children[ i ], geometries, materials ) ); + + } + + } + + if ( data.type === 'LOD' ) { + + var levels = data.levels; + + for ( var l = 0; l < levels.length; l ++ ) { + + var level = levels[ l ]; + var child = object.getObjectByProperty( 'uuid', level.object ); + + if ( child !== undefined ) { + + object.addLevel( child, level.distance ); + + } + + } + + } + + return object; + + } + + } ); + + var TEXTURE_MAPPING = { + UVMapping: UVMapping, + CubeReflectionMapping: CubeReflectionMapping, + CubeRefractionMapping: CubeRefractionMapping, + EquirectangularReflectionMapping: EquirectangularReflectionMapping, + EquirectangularRefractionMapping: EquirectangularRefractionMapping, + SphericalReflectionMapping: SphericalReflectionMapping, + CubeUVReflectionMapping: CubeUVReflectionMapping, + CubeUVRefractionMapping: CubeUVRefractionMapping + }; + + var TEXTURE_WRAPPING = { + RepeatWrapping: RepeatWrapping, + ClampToEdgeWrapping: ClampToEdgeWrapping, + MirroredRepeatWrapping: MirroredRepeatWrapping + }; + + var TEXTURE_FILTER = { + NearestFilter: NearestFilter, + NearestMipMapNearestFilter: NearestMipMapNearestFilter, + NearestMipMapLinearFilter: NearestMipMapLinearFilter, + LinearFilter: LinearFilter, + LinearMipMapNearestFilter: LinearMipMapNearestFilter, + LinearMipMapLinearFilter: LinearMipMapLinearFilter + }; + + /** + * @author thespite / http://clicktorelease.com/ + */ + + function ImageBitmapLoader( manager ) { + + if ( typeof createImageBitmap === 'undefined' ) { + + console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' ); + + } + + if ( typeof fetch === 'undefined' ) { + + console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' ); + + } + + this.manager = manager !== undefined ? manager : DefaultLoadingManager; + this.options = undefined; + + } + + ImageBitmapLoader.prototype = { + + constructor: ImageBitmapLoader, + + setOptions: function setOptions( options ) { + + this.options = options; + + return this; + + }, + + load: function load( url, onLoad, onProgress, onError ) { + + if ( url === undefined ) url = ''; + + if ( this.path !== undefined ) url = this.path + url; + + var scope = this; + + var cached = Cache.get( url ); + + if ( cached !== undefined ) { + + scope.manager.itemStart( url ); + + setTimeout( function () { + + if ( onLoad ) onLoad( cached ); + + scope.manager.itemEnd( url ); + + }, 0 ); + + return cached; + + } + + fetch( url ).then( function ( res ) { + + return res.blob(); + + } ).then( function ( blob ) { + + return createImageBitmap( blob, scope.options ); + + } ).then( function ( imageBitmap ) { + + Cache.add( url, imageBitmap ); + + if ( onLoad ) onLoad( imageBitmap ); + + scope.manager.itemEnd( url ); + + } ).catch( function ( e ) { + + if ( onError ) onError( e ); + + scope.manager.itemEnd( url ); + scope.manager.itemError( url ); + + } ); + + }, + + setCrossOrigin: function ( /* value */ ) { + + return this; + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + }; + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * minimal class for proxing functions to Path. Replaces old "extractSubpaths()" + **/ + + function ShapePath() { + + this.type = 'ShapePath'; + + this.subPaths = []; + this.currentPath = null; + + } + + Object.assign( ShapePath.prototype, { + + moveTo: function ( x, y ) { + + this.currentPath = new Path(); + this.subPaths.push( this.currentPath ); + this.currentPath.moveTo( x, y ); + + }, + + lineTo: function ( x, y ) { + + this.currentPath.lineTo( x, y ); + + }, + + quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) { + + this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY ); + + }, + + bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) { + + this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ); + + }, + + splineThru: function ( pts ) { + + this.currentPath.splineThru( pts ); + + }, + + toShapes: function ( isCCW, noHoles ) { + + function toShapesNoHoles( inSubpaths ) { + + var shapes = []; + + for ( var i = 0, l = inSubpaths.length; i < l; i ++ ) { + + var tmpPath = inSubpaths[ i ]; + + var tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + + shapes.push( tmpShape ); + + } + + return shapes; + + } + + function isPointInsidePolygon( inPt, inPolygon ) { + + var polyLen = inPolygon.length; + + // inPt on polygon contour => immediate success or + // toggling of inside/outside at every single! intersection point of an edge + // with the horizontal line through inPt, left of inPt + // not counting lowerY endpoints of edges and whole edges on that line + var inside = false; + for ( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) { + + var edgeLowPt = inPolygon[ p ]; + var edgeHighPt = inPolygon[ q ]; + + var edgeDx = edgeHighPt.x - edgeLowPt.x; + var edgeDy = edgeHighPt.y - edgeLowPt.y; + + if ( Math.abs( edgeDy ) > Number.EPSILON ) { + + // not parallel + if ( edgeDy < 0 ) { + + edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx; + edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy; + + } + if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue; + + if ( inPt.y === edgeLowPt.y ) { + + if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ? + // continue; // no intersection or edgeLowPt => doesn't count !!! + + } else { + + var perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y ); + if ( perpEdge === 0 ) return true; // inPt is on contour ? + if ( perpEdge < 0 ) continue; + inside = ! inside; // true intersection left of inPt + + } + + } else { + + // parallel or collinear + if ( inPt.y !== edgeLowPt.y ) continue; // parallel + // edge lies on the same horizontal line as inPt + if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) || + ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour ! + // continue; + + } + + } + + return inside; + + } + + var isClockWise = ShapeUtils.isClockWise; + + var subPaths = this.subPaths; + if ( subPaths.length === 0 ) return []; + + if ( noHoles === true ) return toShapesNoHoles( subPaths ); + + + var solid, tmpPath, tmpShape, shapes = []; + + if ( subPaths.length === 1 ) { + + tmpPath = subPaths[ 0 ]; + tmpShape = new Shape(); + tmpShape.curves = tmpPath.curves; + shapes.push( tmpShape ); + return shapes; + + } + + var holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() ); + holesFirst = isCCW ? ! holesFirst : holesFirst; + + // console.log("Holes first", holesFirst); + + var betterShapeHoles = []; + var newShapes = []; + var newShapeHoles = []; + var mainIdx = 0; + var tmpPoints; + + newShapes[ mainIdx ] = undefined; + newShapeHoles[ mainIdx ] = []; + + for ( var i = 0, l = subPaths.length; i < l; i ++ ) { + + tmpPath = subPaths[ i ]; + tmpPoints = tmpPath.getPoints(); + solid = isClockWise( tmpPoints ); + solid = isCCW ? ! solid : solid; + + if ( solid ) { + + if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++; + + newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints }; + newShapes[ mainIdx ].s.curves = tmpPath.curves; + + if ( holesFirst ) mainIdx ++; + newShapeHoles[ mainIdx ] = []; + + //console.log('cw', i); + + } else { + + newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } ); + + //console.log('ccw', i); + + } + + } + + // only Holes? -> probably all Shapes with wrong orientation + if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths ); + + + if ( newShapes.length > 1 ) { + + var ambiguous = false; + var toChange = []; + + for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + + betterShapeHoles[ sIdx ] = []; + + } + + for ( var sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) { + + var sho = newShapeHoles[ sIdx ]; + + for ( var hIdx = 0; hIdx < sho.length; hIdx ++ ) { + + var ho = sho[ hIdx ]; + var hole_unassigned = true; + + for ( var s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) { + + if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) { + + if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } ); + if ( hole_unassigned ) { + + hole_unassigned = false; + betterShapeHoles[ s2Idx ].push( ho ); + + } else { + + ambiguous = true; + + } + + } + + } + if ( hole_unassigned ) { + + betterShapeHoles[ sIdx ].push( ho ); + + } + + } + + } + // console.log("ambiguous: ", ambiguous); + if ( toChange.length > 0 ) { + + // console.log("to change: ", toChange); + if ( ! ambiguous ) newShapeHoles = betterShapeHoles; + + } + + } + + var tmpHoles; + + for ( var i = 0, il = newShapes.length; i < il; i ++ ) { + + tmpShape = newShapes[ i ].s; + shapes.push( tmpShape ); + tmpHoles = newShapeHoles[ i ]; + + for ( var j = 0, jl = tmpHoles.length; j < jl; j ++ ) { + + tmpShape.holes.push( tmpHoles[ j ].h ); + + } + + } + + //console.log("shape", shapes); + + return shapes; + + } + + } ); + + /** + * @author zz85 / http://www.lab4games.net/zz85/blog + * @author mrdoob / http://mrdoob.com/ + */ + + function Font( data ) { + + this.type = 'Font'; + + this.data = data; + + } + + Object.assign( Font.prototype, { + + isFont: true, + + generateShapes: function ( text, size, divisions ) { + + if ( size === undefined ) size = 100; + if ( divisions === undefined ) divisions = 4; + + var shapes = []; + var paths = createPaths( text, size, divisions, this.data ); + + for ( var p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + + } + + } ); + + function createPaths( text, size, divisions, data ) { + + var chars = String( text ).split( '' ); + var scale = size / data.resolution; + var line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; + + var paths = []; + + var offsetX = 0, offsetY = 0; + + for ( var i = 0; i < chars.length; i ++ ) { + + var char = chars[ i ]; + + if ( char === '\n' ) { + + offsetX = 0; + offsetY -= line_height; + + } else { + + var ret = createPath( char, divisions, scale, offsetX, offsetY, data ); + offsetX += ret.offsetX; + paths.push( ret.path ); + + } + + } + + return paths; + + } + + function createPath( char, divisions, scale, offsetX, offsetY, data ) { + + var glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; + + if ( ! glyph ) return; + + var path = new ShapePath(); + + var x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; + + if ( glyph.o ) { + + var outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + + for ( var i = 0, l = outline.length; i < l; ) { + + var action = outline[ i ++ ]; + + switch ( action ) { + + case 'm': // moveTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.moveTo( x, y ); + + break; + + case 'l': // lineTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.lineTo( x, y ); + + break; + + case 'q': // quadraticCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + + break; + + case 'b': // bezierCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + cpx2 = outline[ i ++ ] * scale + offsetX; + cpy2 = outline[ i ++ ] * scale + offsetY; + + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + + break; + + } + + } + + } + + return { offsetX: glyph.ha * scale, path: path }; + + } + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function FontLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( FontLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var scope = this; + + var loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.load( url, function ( text ) { + + var json; + + try { + + json = JSON.parse( text ); + + } catch ( e ) { + + console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' ); + json = JSON.parse( text.substring( 65, text.length - 2 ) ); + + } + + var font = scope.parse( json ); + + if ( onLoad ) onLoad( font ); + + }, onProgress, onError ); + + }, + + parse: function ( json ) { + + return new Font( json ); + + }, + + setPath: function ( value ) { + + this.path = value; + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + var context; + + var AudioContext = { + + getContext: function () { + + if ( context === undefined ) { + + context = new ( window.AudioContext || window.webkitAudioContext )(); + + } + + return context; + + }, + + setContext: function ( value ) { + + context = value; + + } + + }; + + /** + * @author Reece Aaron Lecrivain / http://reecenotes.com/ + */ + + function AudioLoader( manager ) { + + this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager; + + } + + Object.assign( AudioLoader.prototype, { + + load: function ( url, onLoad, onProgress, onError ) { + + var loader = new FileLoader( this.manager ); + loader.setResponseType( 'arraybuffer' ); + loader.load( url, function ( buffer ) { + + var context = AudioContext.getContext(); + + context.decodeAudioData( buffer, function ( audioBuffer ) { + + onLoad( audioBuffer ); + + } ); + + }, onProgress, onError ); + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function StereoCamera() { + + this.type = 'StereoCamera'; + + this.aspect = 1; + + this.eyeSep = 0.064; + + this.cameraL = new PerspectiveCamera(); + this.cameraL.layers.enable( 1 ); + this.cameraL.matrixAutoUpdate = false; + + this.cameraR = new PerspectiveCamera(); + this.cameraR.layers.enable( 2 ); + this.cameraR.matrixAutoUpdate = false; + + } + + Object.assign( StereoCamera.prototype, { + + update: ( function () { + + var instance, focus, fov, aspect, near, far, zoom, eyeSep; + + var eyeRight = new Matrix4(); + var eyeLeft = new Matrix4(); + + return function update( camera ) { + + var needsUpdate = instance !== this || focus !== camera.focus || fov !== camera.fov || + aspect !== camera.aspect * this.aspect || near !== camera.near || + far !== camera.far || zoom !== camera.zoom || eyeSep !== this.eyeSep; + + if ( needsUpdate ) { + + instance = this; + focus = camera.focus; + fov = camera.fov; + aspect = camera.aspect * this.aspect; + near = camera.near; + far = camera.far; + zoom = camera.zoom; + + // Off-axis stereoscopic effect based on + // http://paulbourke.net/stereographics/stereorender/ + + var projectionMatrix = camera.projectionMatrix.clone(); + eyeSep = this.eyeSep / 2; + var eyeSepOnProjection = eyeSep * near / focus; + var ymax = ( near * Math.tan( _Math.DEG2RAD * fov * 0.5 ) ) / zoom; + var xmin, xmax; + + // translate xOffset + + eyeLeft.elements[ 12 ] = - eyeSep; + eyeRight.elements[ 12 ] = eyeSep; + + // for left eye + + xmin = - ymax * aspect + eyeSepOnProjection; + xmax = ymax * aspect + eyeSepOnProjection; + + projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin ); + projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + + this.cameraL.projectionMatrix.copy( projectionMatrix ); + + // for right eye + + xmin = - ymax * aspect - eyeSepOnProjection; + xmax = ymax * aspect - eyeSepOnProjection; + + projectionMatrix.elements[ 0 ] = 2 * near / ( xmax - xmin ); + projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin ); + + this.cameraR.projectionMatrix.copy( projectionMatrix ); + + } + + this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( eyeLeft ); + this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( eyeRight ); + + }; + + } )() + + } ); + + /** + * Camera for rendering cube maps + * - renders scene into axis-aligned cube + * + * @author alteredq / http://alteredqualia.com/ + */ + + function CubeCamera( near, far, cubeResolution ) { + + Object3D.call( this ); + + this.type = 'CubeCamera'; + + var fov = 90, aspect = 1; + + var cameraPX = new PerspectiveCamera( fov, aspect, near, far ); + cameraPX.up.set( 0, - 1, 0 ); + cameraPX.lookAt( new Vector3( 1, 0, 0 ) ); + this.add( cameraPX ); + + var cameraNX = new PerspectiveCamera( fov, aspect, near, far ); + cameraNX.up.set( 0, - 1, 0 ); + cameraNX.lookAt( new Vector3( - 1, 0, 0 ) ); + this.add( cameraNX ); + + var cameraPY = new PerspectiveCamera( fov, aspect, near, far ); + cameraPY.up.set( 0, 0, 1 ); + cameraPY.lookAt( new Vector3( 0, 1, 0 ) ); + this.add( cameraPY ); + + var cameraNY = new PerspectiveCamera( fov, aspect, near, far ); + cameraNY.up.set( 0, 0, - 1 ); + cameraNY.lookAt( new Vector3( 0, - 1, 0 ) ); + this.add( cameraNY ); + + var cameraPZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraPZ.up.set( 0, - 1, 0 ); + cameraPZ.lookAt( new Vector3( 0, 0, 1 ) ); + this.add( cameraPZ ); + + var cameraNZ = new PerspectiveCamera( fov, aspect, near, far ); + cameraNZ.up.set( 0, - 1, 0 ); + cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) ); + this.add( cameraNZ ); + + var options = { format: RGBFormat, magFilter: LinearFilter, minFilter: LinearFilter }; + + this.renderTarget = new WebGLRenderTargetCube( cubeResolution, cubeResolution, options ); + this.renderTarget.texture.name = "CubeCamera"; + + this.update = function ( renderer, scene ) { + + if ( this.parent === null ) this.updateMatrixWorld(); + + var renderTarget = this.renderTarget; + var generateMipmaps = renderTarget.texture.generateMipmaps; + + renderTarget.texture.generateMipmaps = false; + + renderTarget.activeCubeFace = 0; + renderer.render( scene, cameraPX, renderTarget ); + + renderTarget.activeCubeFace = 1; + renderer.render( scene, cameraNX, renderTarget ); + + renderTarget.activeCubeFace = 2; + renderer.render( scene, cameraPY, renderTarget ); + + renderTarget.activeCubeFace = 3; + renderer.render( scene, cameraNY, renderTarget ); + + renderTarget.activeCubeFace = 4; + renderer.render( scene, cameraPZ, renderTarget ); + + renderTarget.texture.generateMipmaps = generateMipmaps; + + renderTarget.activeCubeFace = 5; + renderer.render( scene, cameraNZ, renderTarget ); + + renderer.setRenderTarget( null ); + + }; + + this.clear = function ( renderer, color, depth, stencil ) { + + var renderTarget = this.renderTarget; + + for ( var i = 0; i < 6; i ++ ) { + + renderTarget.activeCubeFace = i; + renderer.setRenderTarget( renderTarget ); + + renderer.clear( color, depth, stencil ); + + } + + renderer.setRenderTarget( null ); + + }; + + } + + CubeCamera.prototype = Object.create( Object3D.prototype ); + CubeCamera.prototype.constructor = CubeCamera; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function AudioListener() { + + Object3D.call( this ); + + this.type = 'AudioListener'; + + this.context = AudioContext.getContext(); + + this.gain = this.context.createGain(); + this.gain.connect( this.context.destination ); + + this.filter = null; + + } + + AudioListener.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: AudioListener, + + getInput: function () { + + return this.gain; + + }, + + removeFilter: function ( ) { + + if ( this.filter !== null ) { + + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); + this.gain.connect( this.context.destination ); + this.filter = null; + + } + + }, + + getFilter: function () { + + return this.filter; + + }, + + setFilter: function ( value ) { + + if ( this.filter !== null ) { + + this.gain.disconnect( this.filter ); + this.filter.disconnect( this.context.destination ); + + } else { + + this.gain.disconnect( this.context.destination ); + + } + + this.filter = value; + this.gain.connect( this.filter ); + this.filter.connect( this.context.destination ); + + }, + + getMasterVolume: function () { + + return this.gain.gain.value; + + }, + + setMasterVolume: function ( value ) { + + this.gain.gain.value = value; + + }, + + updateMatrixWorld: ( function () { + + var position = new Vector3(); + var quaternion = new Quaternion(); + var scale = new Vector3(); + + var orientation = new Vector3(); + + return function updateMatrixWorld( force ) { + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + var listener = this.context.listener; + var up = this.up; + + this.matrixWorld.decompose( position, quaternion, scale ); + + orientation.set( 0, 0, - 1 ).applyQuaternion( quaternion ); + + if ( listener.positionX ) { + + listener.positionX.setValueAtTime( position.x, this.context.currentTime ); + listener.positionY.setValueAtTime( position.y, this.context.currentTime ); + listener.positionZ.setValueAtTime( position.z, this.context.currentTime ); + listener.forwardX.setValueAtTime( orientation.x, this.context.currentTime ); + listener.forwardY.setValueAtTime( orientation.y, this.context.currentTime ); + listener.forwardZ.setValueAtTime( orientation.z, this.context.currentTime ); + listener.upX.setValueAtTime( up.x, this.context.currentTime ); + listener.upY.setValueAtTime( up.y, this.context.currentTime ); + listener.upZ.setValueAtTime( up.z, this.context.currentTime ); + + } else { + + listener.setPosition( position.x, position.y, position.z ); + listener.setOrientation( orientation.x, orientation.y, orientation.z, up.x, up.y, up.z ); + + } + + }; + + } )() + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Reece Aaron Lecrivain / http://reecenotes.com/ + */ + + function Audio( listener ) { + + Object3D.call( this ); + + this.type = 'Audio'; + + this.context = listener.context; + + this.gain = this.context.createGain(); + this.gain.connect( listener.getInput() ); + + this.autoplay = false; + + this.buffer = null; + this.loop = false; + this.startTime = 0; + this.offset = 0; + this.playbackRate = 1; + this.isPlaying = false; + this.hasPlaybackControl = true; + this.sourceType = 'empty'; + + this.filters = []; + + } + + Audio.prototype = Object.assign( Object.create( Object3D.prototype ), { + + constructor: Audio, + + getOutput: function () { + + return this.gain; + + }, + + setNodeSource: function ( audioNode ) { + + this.hasPlaybackControl = false; + this.sourceType = 'audioNode'; + this.source = audioNode; + this.connect(); + + return this; + + }, + + setBuffer: function ( audioBuffer ) { + + this.buffer = audioBuffer; + this.sourceType = 'buffer'; + + if ( this.autoplay ) this.play(); + + return this; + + }, + + play: function () { + + if ( this.isPlaying === true ) { + + console.warn( 'THREE.Audio: Audio is already playing.' ); + return; + + } + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + var source = this.context.createBufferSource(); + + source.buffer = this.buffer; + source.loop = this.loop; + source.onended = this.onEnded.bind( this ); + source.playbackRate.setValueAtTime( this.playbackRate, this.startTime ); + this.startTime = this.context.currentTime; + source.start( this.startTime, this.offset ); + + this.isPlaying = true; + + this.source = source; + + return this.connect(); + + }, + + pause: function () { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + if ( this.isPlaying === true ) { + + this.source.stop(); + this.offset += ( this.context.currentTime - this.startTime ) * this.playbackRate; + this.isPlaying = false; + + } + + return this; + + }, + + stop: function () { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this.source.stop(); + this.offset = 0; + this.isPlaying = false; + + return this; + + }, + + connect: function () { + + if ( this.filters.length > 0 ) { + + this.source.connect( this.filters[ 0 ] ); + + for ( var i = 1, l = this.filters.length; i < l; i ++ ) { + + this.filters[ i - 1 ].connect( this.filters[ i ] ); + + } + + this.filters[ this.filters.length - 1 ].connect( this.getOutput() ); + + } else { + + this.source.connect( this.getOutput() ); + + } + + return this; + + }, + + disconnect: function () { + + if ( this.filters.length > 0 ) { + + this.source.disconnect( this.filters[ 0 ] ); + + for ( var i = 1, l = this.filters.length; i < l; i ++ ) { + + this.filters[ i - 1 ].disconnect( this.filters[ i ] ); + + } + + this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() ); + + } else { + + this.source.disconnect( this.getOutput() ); + + } + + return this; + + }, + + getFilters: function () { + + return this.filters; + + }, + + setFilters: function ( value ) { + + if ( ! value ) value = []; + + if ( this.isPlaying === true ) { + + this.disconnect(); + this.filters = value; + this.connect(); + + } else { + + this.filters = value; + + } + + return this; + + }, + + getFilter: function () { + + return this.getFilters()[ 0 ]; + + }, + + setFilter: function ( filter ) { + + return this.setFilters( filter ? [ filter ] : [] ); + + }, + + setPlaybackRate: function ( value ) { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this.playbackRate = value; + + if ( this.isPlaying === true ) { + + this.source.playbackRate.setValueAtTime( this.playbackRate, this.context.currentTime ); + + } + + return this; + + }, + + getPlaybackRate: function () { + + return this.playbackRate; + + }, + + onEnded: function () { + + this.isPlaying = false; + + }, + + getLoop: function () { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return false; + + } + + return this.loop; + + }, + + setLoop: function ( value ) { + + if ( this.hasPlaybackControl === false ) { + + console.warn( 'THREE.Audio: this Audio has no playback control.' ); + return; + + } + + this.loop = value; + + if ( this.isPlaying === true ) { + + this.source.loop = this.loop; + + } + + return this; + + }, + + getVolume: function () { + + return this.gain.gain.value; + + }, + + setVolume: function ( value ) { + + this.gain.gain.value = value; + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function PositionalAudio( listener ) { + + Audio.call( this, listener ); + + this.panner = this.context.createPanner(); + this.panner.connect( this.gain ); + + } + + PositionalAudio.prototype = Object.assign( Object.create( Audio.prototype ), { + + constructor: PositionalAudio, + + getOutput: function () { + + return this.panner; + + }, + + getRefDistance: function () { + + return this.panner.refDistance; + + }, + + setRefDistance: function ( value ) { + + this.panner.refDistance = value; + + }, + + getRolloffFactor: function () { + + return this.panner.rolloffFactor; + + }, + + setRolloffFactor: function ( value ) { + + this.panner.rolloffFactor = value; + + }, + + getDistanceModel: function () { + + return this.panner.distanceModel; + + }, + + setDistanceModel: function ( value ) { + + this.panner.distanceModel = value; + + }, + + getMaxDistance: function () { + + return this.panner.maxDistance; + + }, + + setMaxDistance: function ( value ) { + + this.panner.maxDistance = value; + + }, + + updateMatrixWorld: ( function () { + + var position = new Vector3(); + + return function updateMatrixWorld( force ) { + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + position.setFromMatrixPosition( this.matrixWorld ); + + this.panner.setPosition( position.x, position.y, position.z ); + + }; + + } )() + + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function AudioAnalyser( audio, fftSize ) { + + this.analyser = audio.context.createAnalyser(); + this.analyser.fftSize = fftSize !== undefined ? fftSize : 2048; + + this.data = new Uint8Array( this.analyser.frequencyBinCount ); + + audio.getOutput().connect( this.analyser ); + + } + + Object.assign( AudioAnalyser.prototype, { + + getFrequencyData: function () { + + this.analyser.getByteFrequencyData( this.data ); + + return this.data; + + }, + + getAverageFrequency: function () { + + var value = 0, data = this.getFrequencyData(); + + for ( var i = 0; i < data.length; i ++ ) { + + value += data[ i ]; + + } + + return value / data.length; + + } + + } ); + + /** + * + * Buffered scene graph property that allows weighted accumulation. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function PropertyMixer( binding, typeName, valueSize ) { + + this.binding = binding; + this.valueSize = valueSize; + + var bufferType = Float64Array, + mixFunction; + + switch ( typeName ) { + + case 'quaternion': + mixFunction = this._slerp; + break; + + case 'string': + case 'bool': + bufferType = Array; + mixFunction = this._select; + break; + + default: + mixFunction = this._lerp; + + } + + this.buffer = new bufferType( valueSize * 4 ); + // layout: [ incoming | accu0 | accu1 | orig ] + // + // interpolators can use .buffer as their .result + // the data then goes to 'incoming' + // + // 'accu0' and 'accu1' are used frame-interleaved for + // the cumulative result and are compared to detect + // changes + // + // 'orig' stores the original state of the property + + this._mixBufferRegion = mixFunction; + + this.cumulativeWeight = 0; + + this.useCount = 0; + this.referenceCount = 0; + + } + + Object.assign( PropertyMixer.prototype, { + + // accumulate data in the 'incoming' region into 'accu' + accumulate: function ( accuIndex, weight ) { + + // note: happily accumulating nothing when weight = 0, the caller knows + // the weight and shouldn't have made the call in the first place + + var buffer = this.buffer, + stride = this.valueSize, + offset = accuIndex * stride + stride, + + currentWeight = this.cumulativeWeight; + + if ( currentWeight === 0 ) { + + // accuN := incoming * weight + + for ( var i = 0; i !== stride; ++ i ) { + + buffer[ offset + i ] = buffer[ i ]; + + } + + currentWeight = weight; + + } else { + + // accuN := accuN + incoming * weight + + currentWeight += weight; + var mix = weight / currentWeight; + this._mixBufferRegion( buffer, offset, 0, mix, stride ); + + } + + this.cumulativeWeight = currentWeight; + + }, + + // apply the state of 'accu' to the binding when accus differ + apply: function ( accuIndex ) { + + var stride = this.valueSize, + buffer = this.buffer, + offset = accuIndex * stride + stride, + + weight = this.cumulativeWeight, + + binding = this.binding; + + this.cumulativeWeight = 0; + + if ( weight < 1 ) { + + // accuN := accuN + original * ( 1 - cumulativeWeight ) + + var originalValueOffset = stride * 3; + + this._mixBufferRegion( + buffer, offset, originalValueOffset, 1 - weight, stride ); + + } + + for ( var i = stride, e = stride + stride; i !== e; ++ i ) { + + if ( buffer[ i ] !== buffer[ i + stride ] ) { + + // value has changed -> update scene graph + + binding.setValue( buffer, offset ); + break; + + } + + } + + }, + + // remember the state of the bound property and copy it to both accus + saveOriginalState: function () { + + var binding = this.binding; + + var buffer = this.buffer, + stride = this.valueSize, + + originalValueOffset = stride * 3; + + binding.getValue( buffer, originalValueOffset ); + + // accu[0..1] := orig -- initially detect changes against the original + for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) { + + buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ]; + + } + + this.cumulativeWeight = 0; + + }, + + // apply the state previously taken via 'saveOriginalState' to the binding + restoreOriginalState: function () { + + var originalValueOffset = this.valueSize * 3; + this.binding.setValue( this.buffer, originalValueOffset ); + + }, + + + // mix functions + + _select: function ( buffer, dstOffset, srcOffset, t, stride ) { + + if ( t >= 0.5 ) { + + for ( var i = 0; i !== stride; ++ i ) { + + buffer[ dstOffset + i ] = buffer[ srcOffset + i ]; + + } + + } + + }, + + _slerp: function ( buffer, dstOffset, srcOffset, t ) { + + Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t ); + + }, + + _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) { + + var s = 1 - t; + + for ( var i = 0; i !== stride; ++ i ) { + + var j = dstOffset + i; + + buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t; + + } + + } + + } ); + + /** + * + * A reference to a real property in the scene graph. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + // Characters [].:/ are reserved for track binding syntax. + var RESERVED_CHARS_RE = '\\[\\]\\.:\\/'; + + function Composite( targetGroup, path, optionalParsedPath ) { + + var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path ); + + this._targetGroup = targetGroup; + this._bindings = targetGroup.subscribe_( path, parsedPath ); + + } + + Object.assign( Composite.prototype, { + + getValue: function ( array, offset ) { + + this.bind(); // bind all binding + + var firstValidIndex = this._targetGroup.nCachedObjects_, + binding = this._bindings[ firstValidIndex ]; + + // and only call .getValue on the first + if ( binding !== undefined ) binding.getValue( array, offset ); + + }, + + setValue: function ( array, offset ) { + + var bindings = this._bindings; + + for ( var i = this._targetGroup.nCachedObjects_, + n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].setValue( array, offset ); + + } + + }, + + bind: function () { + + var bindings = this._bindings; + + for ( var i = this._targetGroup.nCachedObjects_, + n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].bind(); + + } + + }, + + unbind: function () { + + var bindings = this._bindings; + + for ( var i = this._targetGroup.nCachedObjects_, + n = bindings.length; i !== n; ++ i ) { + + bindings[ i ].unbind(); + + } + + } + + } ); + + + function PropertyBinding( rootNode, path, parsedPath ) { + + this.path = path; + this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path ); + + this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode; + + this.rootNode = rootNode; + + } + + Object.assign( PropertyBinding, { + + Composite: Composite, + + create: function ( root, path, parsedPath ) { + + if ( ! ( root && root.isAnimationObjectGroup ) ) { + + return new PropertyBinding( root, path, parsedPath ); + + } else { + + return new PropertyBinding.Composite( root, path, parsedPath ); + + } + + }, + + /** + * Replaces spaces with underscores and removes unsupported characters from + * node names, to ensure compatibility with parseTrackName(). + * + * @param {string} name Node name to be sanitized. + * @return {string} + */ + sanitizeNodeName: ( function () { + + var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' ); + + return function sanitizeNodeName( name ) { + + return name.replace( /\s/g, '_' ).replace( reservedRe, '' ); + + }; + + }() ), + + parseTrackName: function () { + + // Attempts to allow node names from any language. ES5's `\w` regexp matches + // only latin characters, and the unicode \p{L} is not yet supported. So + // instead, we exclude reserved characters and match everything else. + var wordChar = '[^' + RESERVED_CHARS_RE + ']'; + var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\.', '' ) + ']'; + + // Parent directories, delimited by '/' or ':'. Currently unused, but must + // be matched to parse the rest of the track name. + var directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', wordChar ); + + // Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'. + var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot ); + + // Object on target node, and accessor. May not contain reserved + // characters. Accessor may contain any character except closing bracket. + var objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', wordChar ); + + // Property and accessor. May not contain reserved characters. Accessor may + // contain any non-bracket characters. + var propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', wordChar ); + + var trackRe = new RegExp( '' + + '^' + + directoryRe + + nodeRe + + objectRe + + propertyRe + + '$' + ); + + var supportedObjectNames = [ 'material', 'materials', 'bones' ]; + + return function parseTrackName( trackName ) { + + var matches = trackRe.exec( trackName ); + + if ( ! matches ) { + + throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName ); + + } + + var results = { + // directoryName: matches[ 1 ], // (tschw) currently unused + nodeName: matches[ 2 ], + objectName: matches[ 3 ], + objectIndex: matches[ 4 ], + propertyName: matches[ 5 ], // required + propertyIndex: matches[ 6 ] + }; + + var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' ); + + if ( lastDot !== undefined && lastDot !== - 1 ) { + + var objectName = results.nodeName.substring( lastDot + 1 ); + + // Object names must be checked against a whitelist. Otherwise, there + // is no way to parse 'foo.bar.baz': 'baz' must be a property, but + // 'bar' could be the objectName, or part of a nodeName (which can + // include '.' characters). + if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) { + + results.nodeName = results.nodeName.substring( 0, lastDot ); + results.objectName = objectName; + + } + + } + + if ( results.propertyName === null || results.propertyName.length === 0 ) { + + throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName ); + + } + + return results; + + }; + + }(), + + findNode: function ( root, nodeName ) { + + if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) { + + return root; + + } + + // search into skeleton bones. + if ( root.skeleton ) { + + var bone = root.skeleton.getBoneByName( nodeName ); + + if ( bone !== undefined ) { + + return bone; + + } + + } + + // search into node subtree. + if ( root.children ) { + + var searchNodeSubtree = function ( children ) { + + for ( var i = 0; i < children.length; i ++ ) { + + var childNode = children[ i ]; + + if ( childNode.name === nodeName || childNode.uuid === nodeName ) { + + return childNode; + + } + + var result = searchNodeSubtree( childNode.children ); + + if ( result ) return result; + + } + + return null; + + }; + + var subTreeNode = searchNodeSubtree( root.children ); + + if ( subTreeNode ) { + + return subTreeNode; + + } + + } + + return null; + + } + + } ); + + Object.assign( PropertyBinding.prototype, { // prototype, continued + + // these are used to "bind" a nonexistent property + _getValue_unavailable: function () {}, + _setValue_unavailable: function () {}, + + BindingType: { + Direct: 0, + EntireArray: 1, + ArrayElement: 2, + HasFromToArray: 3 + }, + + Versioning: { + None: 0, + NeedsUpdate: 1, + MatrixWorldNeedsUpdate: 2 + }, + + GetterByBindingType: [ + + function getValue_direct( buffer, offset ) { + + buffer[ offset ] = this.node[ this.propertyName ]; + + }, + + function getValue_array( buffer, offset ) { + + var source = this.resolvedProperty; + + for ( var i = 0, n = source.length; i !== n; ++ i ) { + + buffer[ offset ++ ] = source[ i ]; + + } + + }, + + function getValue_arrayElement( buffer, offset ) { + + buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ]; + + }, + + function getValue_toArray( buffer, offset ) { + + this.resolvedProperty.toArray( buffer, offset ); + + } + + ], + + SetterByBindingTypeAndVersioning: [ + + [ + // Direct + + function setValue_direct( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + + }, + + function setValue_direct_setNeedsUpdate( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; + + }, + + function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.targetObject[ this.propertyName ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + ], [ + + // EntireArray + + function setValue_array( buffer, offset ) { + + var dest = this.resolvedProperty; + + for ( var i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + }, + + function setValue_array_setNeedsUpdate( buffer, offset ) { + + var dest = this.resolvedProperty; + + for ( var i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + this.targetObject.needsUpdate = true; + + }, + + function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) { + + var dest = this.resolvedProperty; + + for ( var i = 0, n = dest.length; i !== n; ++ i ) { + + dest[ i ] = buffer[ offset ++ ]; + + } + + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + ], [ + + // ArrayElement + + function setValue_arrayElement( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + + }, + + function setValue_arrayElement_setNeedsUpdate( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.needsUpdate = true; + + }, + + function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ]; + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + ], [ + + // HasToFromArray + + function setValue_fromArray( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + + }, + + function setValue_fromArray_setNeedsUpdate( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.needsUpdate = true; + + }, + + function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) { + + this.resolvedProperty.fromArray( buffer, offset ); + this.targetObject.matrixWorldNeedsUpdate = true; + + } + + ] + + ], + + getValue: function getValue_unbound( targetArray, offset ) { + + this.bind(); + this.getValue( targetArray, offset ); + + // Note: This class uses a State pattern on a per-method basis: + // 'bind' sets 'this.getValue' / 'setValue' and shadows the + // prototype version of these methods with one that represents + // the bound state. When the property is not found, the methods + // become no-ops. + + }, + + setValue: function getValue_unbound( sourceArray, offset ) { + + this.bind(); + this.setValue( sourceArray, offset ); + + }, + + // create getter / setter pair for a property in the scene graph + bind: function () { + + var targetObject = this.node, + parsedPath = this.parsedPath, + + objectName = parsedPath.objectName, + propertyName = parsedPath.propertyName, + propertyIndex = parsedPath.propertyIndex; + + if ( ! targetObject ) { + + targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode; + + this.node = targetObject; + + } + + // set fail state so we can just 'return' on error + this.getValue = this._getValue_unavailable; + this.setValue = this._setValue_unavailable; + + // ensure there is a value node + if ( ! targetObject ) { + + console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' ); + return; + + } + + if ( objectName ) { + + var objectIndex = parsedPath.objectIndex; + + // special cases were we need to reach deeper into the hierarchy to get the face materials.... + switch ( objectName ) { + + case 'materials': + + if ( ! targetObject.material ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this ); + return; + + } + + if ( ! targetObject.material.materials ) { + + console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this ); + return; + + } + + targetObject = targetObject.material.materials; + + break; + + case 'bones': + + if ( ! targetObject.skeleton ) { + + console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this ); + return; + + } + + // potential future optimization: skip this if propertyIndex is already an integer + // and convert the integer string to a true integer. + + targetObject = targetObject.skeleton.bones; + + // support resolving morphTarget names into indices. + for ( var i = 0; i < targetObject.length; i ++ ) { + + if ( targetObject[ i ].name === objectIndex ) { + + objectIndex = i; + break; + + } + + } + + break; + + default: + + if ( targetObject[ objectName ] === undefined ) { + + console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this ); + return; + + } + + targetObject = targetObject[ objectName ]; + + } + + + if ( objectIndex !== undefined ) { + + if ( targetObject[ objectIndex ] === undefined ) { + + console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject ); + return; + + } + + targetObject = targetObject[ objectIndex ]; + + } + + } + + // resolve property + var nodeProperty = targetObject[ propertyName ]; + + if ( nodeProperty === undefined ) { + + var nodeName = parsedPath.nodeName; + + console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName + + '.' + propertyName + ' but it wasn\'t found.', targetObject ); + return; + + } + + // determine versioning scheme + var versioning = this.Versioning.None; + + if ( targetObject.needsUpdate !== undefined ) { // material + + versioning = this.Versioning.NeedsUpdate; + this.targetObject = targetObject; + + } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform + + versioning = this.Versioning.MatrixWorldNeedsUpdate; + this.targetObject = targetObject; + + } + + // determine how the property gets bound + var bindingType = this.BindingType.Direct; + + if ( propertyIndex !== undefined ) { + + // access a sub element of the property array (only primitives are supported right now) + + if ( propertyName === "morphTargetInfluences" ) { + + // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer. + + // support resolving morphTarget names into indices. + if ( ! targetObject.geometry ) { + + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this ); + return; + + } + + if ( targetObject.geometry.isBufferGeometry ) { + + if ( ! targetObject.geometry.morphAttributes ) { + + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this ); + return; + + } + + for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) { + + if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) { + + propertyIndex = i; + break; + + } + + } + + + } else { + + if ( ! targetObject.geometry.morphTargets ) { + + console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this ); + return; + + } + + for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) { + + if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) { + + propertyIndex = i; + break; + + } + + } + + } + + } + + bindingType = this.BindingType.ArrayElement; + + this.resolvedProperty = nodeProperty; + this.propertyIndex = propertyIndex; + + } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) { + + // must use copy for Object3D.Euler/Quaternion + + bindingType = this.BindingType.HasFromToArray; + + this.resolvedProperty = nodeProperty; + + } else if ( Array.isArray( nodeProperty ) ) { + + bindingType = this.BindingType.EntireArray; + + this.resolvedProperty = nodeProperty; + + } else { + + this.propertyName = propertyName; + + } + + // select getter / setter + this.getValue = this.GetterByBindingType[ bindingType ]; + this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ]; + + }, + + unbind: function () { + + this.node = null; + + // back to the prototype version of getValue / setValue + // note: avoiding to mutate the shape of 'this' via 'delete' + this.getValue = this._getValue_unbound; + this.setValue = this._setValue_unbound; + + } + + } ); + + //!\ DECLARE ALIAS AFTER assign prototype ! + Object.assign( PropertyBinding.prototype, { + + // initial state of these methods that calls 'bind' + _getValue_unbound: PropertyBinding.prototype.getValue, + _setValue_unbound: PropertyBinding.prototype.setValue, + + } ); + + /** + * + * A group of objects that receives a shared animation state. + * + * Usage: + * + * - Add objects you would otherwise pass as 'root' to the + * constructor or the .clipAction method of AnimationMixer. + * + * - Instead pass this object as 'root'. + * + * - You can also add and remove objects later when the mixer + * is running. + * + * Note: + * + * Objects of this class appear as one object to the mixer, + * so cache control of the individual objects must be done + * on the group. + * + * Limitation: + * + * - The animated properties must be compatible among the + * all objects in the group. + * + * - A single property can either be controlled through a + * target group or directly, but not both. + * + * @author tschw + */ + + function AnimationObjectGroup() { + + this.uuid = _Math.generateUUID(); + + // cached objects followed by the active ones + this._objects = Array.prototype.slice.call( arguments ); + + this.nCachedObjects_ = 0; // threshold + // note: read by PropertyBinding.Composite + + var indices = {}; + this._indicesByUUID = indices; // for bookkeeping + + for ( var i = 0, n = arguments.length; i !== n; ++ i ) { + + indices[ arguments[ i ].uuid ] = i; + + } + + this._paths = []; // inside: string + this._parsedPaths = []; // inside: { we don't care, here } + this._bindings = []; // inside: Array< PropertyBinding > + this._bindingsIndicesByPath = {}; // inside: indices in these arrays + + var scope = this; + + this.stats = { + + objects: { + get total() { + + return scope._objects.length; + + }, + get inUse() { + + return this.total - scope.nCachedObjects_; + + } + }, + get bindingsPerObject() { + + return scope._bindings.length; + + } + + }; + + } + + Object.assign( AnimationObjectGroup.prototype, { + + isAnimationObjectGroup: true, + + add: function () { + + var objects = this._objects, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_, + indicesByUUID = this._indicesByUUID, + paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + nBindings = bindings.length, + knownObject = undefined; + + for ( var i = 0, n = arguments.length; i !== n; ++ i ) { + + var object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; + + if ( index === undefined ) { + + // unknown object -> add it to the ACTIVE region + + index = nObjects ++; + indicesByUUID[ uuid ] = index; + objects.push( object ); + + // accounting is done, now do the same for all bindings + + for ( var j = 0, m = nBindings; j !== m; ++ j ) { + + bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); + + } + + } else if ( index < nCachedObjects ) { + + knownObject = objects[ index ]; + + // move existing object to the ACTIVE region + + var firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ]; + + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; + + indicesByUUID[ uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = object; + + // accounting is done, now do the same for all bindings + + for ( var j = 0, m = nBindings; j !== m; ++ j ) { + + var bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ], + binding = bindingsForPath[ index ]; + + bindingsForPath[ index ] = lastCached; + + if ( binding === undefined ) { + + // since we do not bother to create new bindings + // for objects that are cached, the binding may + // or may not exist + + binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); + + } + + bindingsForPath[ firstActiveIndex ] = binding; + + } + + } else if ( objects[ index ] !== knownObject ) { + + console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + + 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); + + } // else the object is already where we want it to be + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + }, + + remove: function () { + + var objects = this._objects, + nCachedObjects = this.nCachedObjects_, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; + + for ( var i = 0, n = arguments.length; i !== n; ++ i ) { + + var object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; + + if ( index !== undefined && index >= nCachedObjects ) { + + // move existing object into the CACHED region + + var lastCachedIndex = nCachedObjects ++, + firstActiveObject = objects[ lastCachedIndex ]; + + indicesByUUID[ firstActiveObject.uuid ] = index; + objects[ index ] = firstActiveObject; + + indicesByUUID[ uuid ] = lastCachedIndex; + objects[ lastCachedIndex ] = object; + + // accounting is done, now do the same for all bindings + + for ( var j = 0, m = nBindings; j !== m; ++ j ) { + + var bindingsForPath = bindings[ j ], + firstActive = bindingsForPath[ lastCachedIndex ], + binding = bindingsForPath[ index ]; + + bindingsForPath[ index ] = firstActive; + bindingsForPath[ lastCachedIndex ] = binding; + + } + + } + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + }, + + // remove & forget + uncache: function () { + + var objects = this._objects, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_, + indicesByUUID = this._indicesByUUID, + bindings = this._bindings, + nBindings = bindings.length; + + for ( var i = 0, n = arguments.length; i !== n; ++ i ) { + + var object = arguments[ i ], + uuid = object.uuid, + index = indicesByUUID[ uuid ]; + + if ( index !== undefined ) { + + delete indicesByUUID[ uuid ]; + + if ( index < nCachedObjects ) { + + // object is cached, shrink the CACHED region + + var firstActiveIndex = -- nCachedObjects, + lastCachedObject = objects[ firstActiveIndex ], + lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; + + // last cached object takes this object's place + indicesByUUID[ lastCachedObject.uuid ] = index; + objects[ index ] = lastCachedObject; + + // last object goes to the activated slot and pop + indicesByUUID[ lastObject.uuid ] = firstActiveIndex; + objects[ firstActiveIndex ] = lastObject; + objects.pop(); + + // accounting is done, now do the same for all bindings + + for ( var j = 0, m = nBindings; j !== m; ++ j ) { + + var bindingsForPath = bindings[ j ], + lastCached = bindingsForPath[ firstActiveIndex ], + last = bindingsForPath[ lastIndex ]; + + bindingsForPath[ index ] = lastCached; + bindingsForPath[ firstActiveIndex ] = last; + bindingsForPath.pop(); + + } + + } else { + + // object is active, just swap with the last and pop + + var lastIndex = -- nObjects, + lastObject = objects[ lastIndex ]; + + indicesByUUID[ lastObject.uuid ] = index; + objects[ index ] = lastObject; + objects.pop(); + + // accounting is done, now do the same for all bindings + + for ( var j = 0, m = nBindings; j !== m; ++ j ) { + + var bindingsForPath = bindings[ j ]; + + bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; + bindingsForPath.pop(); + + } + + } // cached or active + + } // if object is known + + } // for arguments + + this.nCachedObjects_ = nCachedObjects; + + }, + + // Internal interface used by befriended PropertyBinding.Composite: + + subscribe_: function ( path, parsedPath ) { + + // returns an array of bindings for the given path that is changed + // according to the contained objects in the group + + var indicesByPath = this._bindingsIndicesByPath, + index = indicesByPath[ path ], + bindings = this._bindings; + + if ( index !== undefined ) return bindings[ index ]; + + var paths = this._paths, + parsedPaths = this._parsedPaths, + objects = this._objects, + nObjects = objects.length, + nCachedObjects = this.nCachedObjects_, + bindingsForPath = new Array( nObjects ); + + index = bindings.length; + + indicesByPath[ path ] = index; + + paths.push( path ); + parsedPaths.push( parsedPath ); + bindings.push( bindingsForPath ); + + for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) { + + var object = objects[ i ]; + bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); + + } + + return bindingsForPath; + + }, + + unsubscribe_: function ( path ) { + + // tells the group to forget about a property path and no longer + // update the array previously obtained with 'subscribe_' + + var indicesByPath = this._bindingsIndicesByPath, + index = indicesByPath[ path ]; + + if ( index !== undefined ) { + + var paths = this._paths, + parsedPaths = this._parsedPaths, + bindings = this._bindings, + lastBindingsIndex = bindings.length - 1, + lastBindings = bindings[ lastBindingsIndex ], + lastBindingsPath = path[ lastBindingsIndex ]; + + indicesByPath[ lastBindingsPath ] = index; + + bindings[ index ] = lastBindings; + bindings.pop(); + + parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; + parsedPaths.pop(); + + paths[ index ] = paths[ lastBindingsIndex ]; + paths.pop(); + + } + + } + + } ); + + /** + * + * Action provided by AnimationMixer for scheduling clip playback on specific + * objects. + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + * + */ + + function AnimationAction( mixer, clip, localRoot ) { + + this._mixer = mixer; + this._clip = clip; + this._localRoot = localRoot || null; + + var tracks = clip.tracks, + nTracks = tracks.length, + interpolants = new Array( nTracks ); + + var interpolantSettings = { + endingStart: ZeroCurvatureEnding, + endingEnd: ZeroCurvatureEnding + }; + + for ( var i = 0; i !== nTracks; ++ i ) { + + var interpolant = tracks[ i ].createInterpolant( null ); + interpolants[ i ] = interpolant; + interpolant.settings = interpolantSettings; + + } + + this._interpolantSettings = interpolantSettings; + + this._interpolants = interpolants; // bound by the mixer + + // inside: PropertyMixer (managed by the mixer) + this._propertyBindings = new Array( nTracks ); + + this._cacheIndex = null; // for the memory manager + this._byClipCacheIndex = null; // for the memory manager + + this._timeScaleInterpolant = null; + this._weightInterpolant = null; + + this.loop = LoopRepeat; + this._loopCount = - 1; + + // global mixer time when the action is to be started + // it's set back to 'null' upon start of the action + this._startTime = null; + + // scaled local time of the action + // gets clamped or wrapped to 0..clip.duration according to loop + this.time = 0; + + this.timeScale = 1; + this._effectiveTimeScale = 1; + + this.weight = 1; + this._effectiveWeight = 1; + + this.repetitions = Infinity; // no. of repetitions when looping + + this.paused = false; // true -> zero effective time scale + this.enabled = true; // false -> zero effective weight + + this.clampWhenFinished = false; // keep feeding the last frame? + + this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate + this.zeroSlopeAtEnd = true; // clips for start, loop and end + + } + + Object.assign( AnimationAction.prototype, { + + // State & Scheduling + + play: function () { + + this._mixer._activateAction( this ); + + return this; + + }, + + stop: function () { + + this._mixer._deactivateAction( this ); + + return this.reset(); + + }, + + reset: function () { + + this.paused = false; + this.enabled = true; + + this.time = 0; // restart clip + this._loopCount = - 1; // forget previous loops + this._startTime = null; // forget scheduling + + return this.stopFading().stopWarping(); + + }, + + isRunning: function () { + + return this.enabled && ! this.paused && this.timeScale !== 0 && + this._startTime === null && this._mixer._isActiveAction( this ); + + }, + + // return true when play has been called + isScheduled: function () { + + return this._mixer._isActiveAction( this ); + + }, + + startAt: function ( time ) { + + this._startTime = time; + + return this; + + }, + + setLoop: function ( mode, repetitions ) { + + this.loop = mode; + this.repetitions = repetitions; + + return this; + + }, + + // Weight + + // set the weight stopping any scheduled fading + // although .enabled = false yields an effective weight of zero, this + // method does *not* change .enabled, because it would be confusing + setEffectiveWeight: function ( weight ) { + + this.weight = weight; + + // note: same logic as when updated at runtime + this._effectiveWeight = this.enabled ? weight : 0; + + return this.stopFading(); + + }, + + // return the weight considering fading and .enabled + getEffectiveWeight: function () { + + return this._effectiveWeight; + + }, + + fadeIn: function ( duration ) { + + return this._scheduleFading( duration, 0, 1 ); + + }, + + fadeOut: function ( duration ) { + + return this._scheduleFading( duration, 1, 0 ); + + }, + + crossFadeFrom: function ( fadeOutAction, duration, warp ) { + + fadeOutAction.fadeOut( duration ); + this.fadeIn( duration ); + + if ( warp ) { + + var fadeInDuration = this._clip.duration, + fadeOutDuration = fadeOutAction._clip.duration, + + startEndRatio = fadeOutDuration / fadeInDuration, + endStartRatio = fadeInDuration / fadeOutDuration; + + fadeOutAction.warp( 1.0, startEndRatio, duration ); + this.warp( endStartRatio, 1.0, duration ); + + } + + return this; + + }, + + crossFadeTo: function ( fadeInAction, duration, warp ) { + + return fadeInAction.crossFadeFrom( this, duration, warp ); + + }, + + stopFading: function () { + + var weightInterpolant = this._weightInterpolant; + + if ( weightInterpolant !== null ) { + + this._weightInterpolant = null; + this._mixer._takeBackControlInterpolant( weightInterpolant ); + + } + + return this; + + }, + + // Time Scale Control + + // set the time scale stopping any scheduled warping + // although .paused = true yields an effective time scale of zero, this + // method does *not* change .paused, because it would be confusing + setEffectiveTimeScale: function ( timeScale ) { + + this.timeScale = timeScale; + this._effectiveTimeScale = this.paused ? 0 : timeScale; + + return this.stopWarping(); + + }, + + // return the time scale considering warping and .paused + getEffectiveTimeScale: function () { + + return this._effectiveTimeScale; + + }, + + setDuration: function ( duration ) { + + this.timeScale = this._clip.duration / duration; + + return this.stopWarping(); + + }, + + syncWith: function ( action ) { + + this.time = action.time; + this.timeScale = action.timeScale; + + return this.stopWarping(); + + }, + + halt: function ( duration ) { + + return this.warp( this._effectiveTimeScale, 0, duration ); + + }, + + warp: function ( startTimeScale, endTimeScale, duration ) { + + var mixer = this._mixer, now = mixer.time, + interpolant = this._timeScaleInterpolant, + + timeScale = this.timeScale; + + if ( interpolant === null ) { + + interpolant = mixer._lendControlInterpolant(); + this._timeScaleInterpolant = interpolant; + + } + + var times = interpolant.parameterPositions, + values = interpolant.sampleValues; + + times[ 0 ] = now; + times[ 1 ] = now + duration; + + values[ 0 ] = startTimeScale / timeScale; + values[ 1 ] = endTimeScale / timeScale; + + return this; + + }, + + stopWarping: function () { + + var timeScaleInterpolant = this._timeScaleInterpolant; + + if ( timeScaleInterpolant !== null ) { + + this._timeScaleInterpolant = null; + this._mixer._takeBackControlInterpolant( timeScaleInterpolant ); + + } + + return this; + + }, + + // Object Accessors + + getMixer: function () { + + return this._mixer; + + }, + + getClip: function () { + + return this._clip; + + }, + + getRoot: function () { + + return this._localRoot || this._mixer._root; + + }, + + // Interna + + _update: function ( time, deltaTime, timeDirection, accuIndex ) { + + // called by the mixer + + if ( ! this.enabled ) { + + // call ._updateWeight() to update ._effectiveWeight + + this._updateWeight( time ); + return; + + } + + var startTime = this._startTime; + + if ( startTime !== null ) { + + // check for scheduled start of action + + var timeRunning = ( time - startTime ) * timeDirection; + if ( timeRunning < 0 || timeDirection === 0 ) { + + return; // yet to come / don't decide when delta = 0 + + } + + // start + + this._startTime = null; // unschedule + deltaTime = timeDirection * timeRunning; + + } + + // apply time scale and advance time + + deltaTime *= this._updateTimeScale( time ); + var clipTime = this._updateTime( deltaTime ); + + // note: _updateTime may disable the action resulting in + // an effective weight of 0 + + var weight = this._updateWeight( time ); + + if ( weight > 0 ) { + + var interpolants = this._interpolants; + var propertyMixers = this._propertyBindings; + + for ( var j = 0, m = interpolants.length; j !== m; ++ j ) { + + interpolants[ j ].evaluate( clipTime ); + propertyMixers[ j ].accumulate( accuIndex, weight ); + + } + + } + + }, + + _updateWeight: function ( time ) { + + var weight = 0; + + if ( this.enabled ) { + + weight = this.weight; + var interpolant = this._weightInterpolant; + + if ( interpolant !== null ) { + + var interpolantValue = interpolant.evaluate( time )[ 0 ]; + + weight *= interpolantValue; + + if ( time > interpolant.parameterPositions[ 1 ] ) { + + this.stopFading(); + + if ( interpolantValue === 0 ) { + + // faded out, disable + this.enabled = false; + + } + + } + + } + + } + + this._effectiveWeight = weight; + return weight; + + }, + + _updateTimeScale: function ( time ) { + + var timeScale = 0; + + if ( ! this.paused ) { + + timeScale = this.timeScale; + + var interpolant = this._timeScaleInterpolant; + + if ( interpolant !== null ) { + + var interpolantValue = interpolant.evaluate( time )[ 0 ]; + + timeScale *= interpolantValue; + + if ( time > interpolant.parameterPositions[ 1 ] ) { + + this.stopWarping(); + + if ( timeScale === 0 ) { + + // motion has halted, pause + this.paused = true; + + } else { + + // warp done - apply final time scale + this.timeScale = timeScale; + + } + + } + + } + + } + + this._effectiveTimeScale = timeScale; + return timeScale; + + }, + + _updateTime: function ( deltaTime ) { + + var time = this.time + deltaTime; + + if ( deltaTime === 0 ) return time; + + var duration = this._clip.duration, + + loop = this.loop, + loopCount = this._loopCount; + + if ( loop === LoopOnce ) { + + if ( loopCount === - 1 ) { + + // just started + + this._loopCount = 0; + this._setEndings( true, true, false ); + + } + + handle_stop: { + + if ( time >= duration ) { + + time = duration; + + } else if ( time < 0 ) { + + time = 0; + + } else break handle_stop; + + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; + + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime < 0 ? - 1 : 1 + } ); + + } + + } else { // repetitive Repeat or PingPong + + var pingPong = ( loop === LoopPingPong ); + + if ( loopCount === - 1 ) { + + // just started + + if ( deltaTime >= 0 ) { + + loopCount = 0; + + this._setEndings( true, this.repetitions === 0, pingPong ); + + } else { + + // when looping in reverse direction, the initial + // transition through zero counts as a repetition, + // so leave loopCount at -1 + + this._setEndings( this.repetitions === 0, true, pingPong ); + + } + + } + + if ( time >= duration || time < 0 ) { + + // wrap around + + var loopDelta = Math.floor( time / duration ); // signed + time -= duration * loopDelta; + + loopCount += Math.abs( loopDelta ); + + var pending = this.repetitions - loopCount; + + if ( pending <= 0 ) { + + // have to stop (switch state, clamp time, fire event) + + if ( this.clampWhenFinished ) this.paused = true; + else this.enabled = false; + + time = deltaTime > 0 ? duration : 0; + + this._mixer.dispatchEvent( { + type: 'finished', action: this, + direction: deltaTime > 0 ? 1 : - 1 + } ); + + } else { + + // keep running + + if ( pending === 1 ) { + + // entering the last round + + var atStart = deltaTime < 0; + this._setEndings( atStart, ! atStart, pingPong ); + + } else { + + this._setEndings( false, false, pingPong ); + + } + + this._loopCount = loopCount; + + this._mixer.dispatchEvent( { + type: 'loop', action: this, loopDelta: loopDelta + } ); + + } + + } + + if ( pingPong && ( loopCount & 1 ) === 1 ) { + + // invert time for the "pong round" + + this.time = time; + return duration - time; + + } + + } + + this.time = time; + return time; + + }, + + _setEndings: function ( atStart, atEnd, pingPong ) { + + var settings = this._interpolantSettings; + + if ( pingPong ) { + + settings.endingStart = ZeroSlopeEnding; + settings.endingEnd = ZeroSlopeEnding; + + } else { + + // assuming for LoopOnce atStart == atEnd == true + + if ( atStart ) { + + settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding; + + } else { + + settings.endingStart = WrapAroundEnding; + + } + + if ( atEnd ) { + + settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding; + + } else { + + settings.endingEnd = WrapAroundEnding; + + } + + } + + }, + + _scheduleFading: function ( duration, weightNow, weightThen ) { + + var mixer = this._mixer, now = mixer.time, + interpolant = this._weightInterpolant; + + if ( interpolant === null ) { + + interpolant = mixer._lendControlInterpolant(); + this._weightInterpolant = interpolant; + + } + + var times = interpolant.parameterPositions, + values = interpolant.sampleValues; + + times[ 0 ] = now; values[ 0 ] = weightNow; + times[ 1 ] = now + duration; values[ 1 ] = weightThen; + + return this; + + } + + } ); + + /** + * + * Player for AnimationClips. + * + * + * @author Ben Houston / http://clara.io/ + * @author David Sarno / http://lighthaus.us/ + * @author tschw + */ + + function AnimationMixer( root ) { + + this._root = root; + this._initMemoryManager(); + this._accuIndex = 0; + + this.time = 0; + + this.timeScale = 1.0; + + } + + AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), { + + constructor: AnimationMixer, + + _bindAction: function ( action, prototypeAction ) { + + var root = action._localRoot || this._root, + tracks = action._clip.tracks, + nTracks = tracks.length, + bindings = action._propertyBindings, + interpolants = action._interpolants, + rootUuid = root.uuid, + bindingsByRoot = this._bindingsByRootAndName, + bindingsByName = bindingsByRoot[ rootUuid ]; + + if ( bindingsByName === undefined ) { + + bindingsByName = {}; + bindingsByRoot[ rootUuid ] = bindingsByName; + + } + + for ( var i = 0; i !== nTracks; ++ i ) { + + var track = tracks[ i ], + trackName = track.name, + binding = bindingsByName[ trackName ]; + + if ( binding !== undefined ) { + + bindings[ i ] = binding; + + } else { + + binding = bindings[ i ]; + + if ( binding !== undefined ) { + + // existing binding, make sure the cache knows + + if ( binding._cacheIndex === null ) { + + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); + + } + + continue; + + } + + var path = prototypeAction && prototypeAction. + _propertyBindings[ i ].binding.parsedPath; + + binding = new PropertyMixer( + PropertyBinding.create( root, trackName, path ), + track.ValueTypeName, track.getValueSize() ); + + ++ binding.referenceCount; + this._addInactiveBinding( binding, rootUuid, trackName ); + + bindings[ i ] = binding; + + } + + interpolants[ i ].resultBuffer = binding.buffer; + + } + + }, + + _activateAction: function ( action ) { + + if ( ! this._isActiveAction( action ) ) { + + if ( action._cacheIndex === null ) { + + // this action has been forgotten by the cache, but the user + // appears to be still using it -> rebind + + var rootUuid = ( action._localRoot || this._root ).uuid, + clipUuid = action._clip.uuid, + actionsForClip = this._actionsByClip[ clipUuid ]; + + this._bindAction( action, + actionsForClip && actionsForClip.knownActions[ 0 ] ); + + this._addInactiveAction( action, clipUuid, rootUuid ); + + } + + var bindings = action._propertyBindings; + + // increment reference counts / sort out state + for ( var i = 0, n = bindings.length; i !== n; ++ i ) { + + var binding = bindings[ i ]; + + if ( binding.useCount ++ === 0 ) { + + this._lendBinding( binding ); + binding.saveOriginalState(); + + } + + } + + this._lendAction( action ); + + } + + }, + + _deactivateAction: function ( action ) { + + if ( this._isActiveAction( action ) ) { + + var bindings = action._propertyBindings; + + // decrement reference counts / sort out state + for ( var i = 0, n = bindings.length; i !== n; ++ i ) { + + var binding = bindings[ i ]; + + if ( -- binding.useCount === 0 ) { + + binding.restoreOriginalState(); + this._takeBackBinding( binding ); + + } + + } + + this._takeBackAction( action ); + + } + + }, + + // Memory manager + + _initMemoryManager: function () { + + this._actions = []; // 'nActiveActions' followed by inactive ones + this._nActiveActions = 0; + + this._actionsByClip = {}; + // inside: + // { + // knownActions: Array< AnimationAction > - used as prototypes + // actionByRoot: AnimationAction - lookup + // } + + + this._bindings = []; // 'nActiveBindings' followed by inactive ones + this._nActiveBindings = 0; + + this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer > + + + this._controlInterpolants = []; // same game as above + this._nActiveControlInterpolants = 0; + + var scope = this; + + this.stats = { + + actions: { + get total() { + + return scope._actions.length; + + }, + get inUse() { + + return scope._nActiveActions; + + } + }, + bindings: { + get total() { + + return scope._bindings.length; + + }, + get inUse() { + + return scope._nActiveBindings; + + } + }, + controlInterpolants: { + get total() { + + return scope._controlInterpolants.length; + + }, + get inUse() { + + return scope._nActiveControlInterpolants; + + } + } + + }; + + }, + + // Memory management for AnimationAction objects + + _isActiveAction: function ( action ) { + + var index = action._cacheIndex; + return index !== null && index < this._nActiveActions; + + }, + + _addInactiveAction: function ( action, clipUuid, rootUuid ) { + + var actions = this._actions, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ]; + + if ( actionsForClip === undefined ) { + + actionsForClip = { + + knownActions: [ action ], + actionByRoot: {} + + }; + + action._byClipCacheIndex = 0; + + actionsByClip[ clipUuid ] = actionsForClip; + + } else { + + var knownActions = actionsForClip.knownActions; + + action._byClipCacheIndex = knownActions.length; + knownActions.push( action ); + + } + + action._cacheIndex = actions.length; + actions.push( action ); + + actionsForClip.actionByRoot[ rootUuid ] = action; + + }, + + _removeInactiveAction: function ( action ) { + + var actions = this._actions, + lastInactiveAction = actions[ actions.length - 1 ], + cacheIndex = action._cacheIndex; + + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); + + action._cacheIndex = null; + + + var clipUuid = action._clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ], + knownActionsForClip = actionsForClip.knownActions, + + lastKnownAction = + knownActionsForClip[ knownActionsForClip.length - 1 ], + + byClipCacheIndex = action._byClipCacheIndex; + + lastKnownAction._byClipCacheIndex = byClipCacheIndex; + knownActionsForClip[ byClipCacheIndex ] = lastKnownAction; + knownActionsForClip.pop(); + + action._byClipCacheIndex = null; + + + var actionByRoot = actionsForClip.actionByRoot, + rootUuid = ( action._localRoot || this._root ).uuid; + + delete actionByRoot[ rootUuid ]; + + if ( knownActionsForClip.length === 0 ) { + + delete actionsByClip[ clipUuid ]; + + } + + this._removeInactiveBindingsForAction( action ); + + }, + + _removeInactiveBindingsForAction: function ( action ) { + + var bindings = action._propertyBindings; + for ( var i = 0, n = bindings.length; i !== n; ++ i ) { + + var binding = bindings[ i ]; + + if ( -- binding.referenceCount === 0 ) { + + this._removeInactiveBinding( binding ); + + } + + } + + }, + + _lendAction: function ( action ) { + + // [ active actions | inactive actions ] + // [ active actions >| inactive actions ] + // s a + // <-swap-> + // a s + + var actions = this._actions, + prevIndex = action._cacheIndex, + + lastActiveIndex = this._nActiveActions ++, + + firstInactiveAction = actions[ lastActiveIndex ]; + + action._cacheIndex = lastActiveIndex; + actions[ lastActiveIndex ] = action; + + firstInactiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = firstInactiveAction; + + }, + + _takeBackAction: function ( action ) { + + // [ active actions | inactive actions ] + // [ active actions |< inactive actions ] + // a s + // <-swap-> + // s a + + var actions = this._actions, + prevIndex = action._cacheIndex, + + firstInactiveIndex = -- this._nActiveActions, + + lastActiveAction = actions[ firstInactiveIndex ]; + + action._cacheIndex = firstInactiveIndex; + actions[ firstInactiveIndex ] = action; + + lastActiveAction._cacheIndex = prevIndex; + actions[ prevIndex ] = lastActiveAction; + + }, + + // Memory management for PropertyMixer objects + + _addInactiveBinding: function ( binding, rootUuid, trackName ) { + + var bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ], + + bindings = this._bindings; + + if ( bindingByName === undefined ) { + + bindingByName = {}; + bindingsByRoot[ rootUuid ] = bindingByName; + + } + + bindingByName[ trackName ] = binding; + + binding._cacheIndex = bindings.length; + bindings.push( binding ); + + }, + + _removeInactiveBinding: function ( binding ) { + + var bindings = this._bindings, + propBinding = binding.binding, + rootUuid = propBinding.rootNode.uuid, + trackName = propBinding.path, + bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ], + + lastInactiveBinding = bindings[ bindings.length - 1 ], + cacheIndex = binding._cacheIndex; + + lastInactiveBinding._cacheIndex = cacheIndex; + bindings[ cacheIndex ] = lastInactiveBinding; + bindings.pop(); + + delete bindingByName[ trackName ]; + + remove_empty_map: { + + for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars + + delete bindingsByRoot[ rootUuid ]; + + } + + }, + + _lendBinding: function ( binding ) { + + var bindings = this._bindings, + prevIndex = binding._cacheIndex, + + lastActiveIndex = this._nActiveBindings ++, + + firstInactiveBinding = bindings[ lastActiveIndex ]; + + binding._cacheIndex = lastActiveIndex; + bindings[ lastActiveIndex ] = binding; + + firstInactiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = firstInactiveBinding; + + }, + + _takeBackBinding: function ( binding ) { + + var bindings = this._bindings, + prevIndex = binding._cacheIndex, + + firstInactiveIndex = -- this._nActiveBindings, + + lastActiveBinding = bindings[ firstInactiveIndex ]; + + binding._cacheIndex = firstInactiveIndex; + bindings[ firstInactiveIndex ] = binding; + + lastActiveBinding._cacheIndex = prevIndex; + bindings[ prevIndex ] = lastActiveBinding; + + }, + + + // Memory management of Interpolants for weight and time scale + + _lendControlInterpolant: function () { + + var interpolants = this._controlInterpolants, + lastActiveIndex = this._nActiveControlInterpolants ++, + interpolant = interpolants[ lastActiveIndex ]; + + if ( interpolant === undefined ) { + + interpolant = new LinearInterpolant( + new Float32Array( 2 ), new Float32Array( 2 ), + 1, this._controlInterpolantsResultBuffer ); + + interpolant.__cacheIndex = lastActiveIndex; + interpolants[ lastActiveIndex ] = interpolant; + + } + + return interpolant; + + }, + + _takeBackControlInterpolant: function ( interpolant ) { + + var interpolants = this._controlInterpolants, + prevIndex = interpolant.__cacheIndex, + + firstInactiveIndex = -- this._nActiveControlInterpolants, + + lastActiveInterpolant = interpolants[ firstInactiveIndex ]; + + interpolant.__cacheIndex = firstInactiveIndex; + interpolants[ firstInactiveIndex ] = interpolant; + + lastActiveInterpolant.__cacheIndex = prevIndex; + interpolants[ prevIndex ] = lastActiveInterpolant; + + }, + + _controlInterpolantsResultBuffer: new Float32Array( 1 ), + + // return an action for a clip optionally using a custom root target + // object (this method allocates a lot of dynamic memory in case a + // previously unknown clip/root combination is specified) + clipAction: function ( clip, optionalRoot ) { + + var root = optionalRoot || this._root, + rootUuid = root.uuid, + + clipObject = typeof clip === 'string' ? + AnimationClip.findByName( root, clip ) : clip, + + clipUuid = clipObject !== null ? clipObject.uuid : clip, + + actionsForClip = this._actionsByClip[ clipUuid ], + prototypeAction = null; + + if ( actionsForClip !== undefined ) { + + var existingAction = + actionsForClip.actionByRoot[ rootUuid ]; + + if ( existingAction !== undefined ) { + + return existingAction; + + } + + // we know the clip, so we don't have to parse all + // the bindings again but can just copy + prototypeAction = actionsForClip.knownActions[ 0 ]; + + // also, take the clip from the prototype action + if ( clipObject === null ) + clipObject = prototypeAction._clip; + + } + + // clip must be known when specified via string + if ( clipObject === null ) return null; + + // allocate all resources required to run it + var newAction = new AnimationAction( this, clipObject, optionalRoot ); + + this._bindAction( newAction, prototypeAction ); + + // and make the action known to the memory manager + this._addInactiveAction( newAction, clipUuid, rootUuid ); + + return newAction; + + }, + + // get an existing action + existingAction: function ( clip, optionalRoot ) { + + var root = optionalRoot || this._root, + rootUuid = root.uuid, + + clipObject = typeof clip === 'string' ? + AnimationClip.findByName( root, clip ) : clip, + + clipUuid = clipObject ? clipObject.uuid : clip, + + actionsForClip = this._actionsByClip[ clipUuid ]; + + if ( actionsForClip !== undefined ) { + + return actionsForClip.actionByRoot[ rootUuid ] || null; + + } + + return null; + + }, + + // deactivates all previously scheduled actions + stopAllAction: function () { + + var actions = this._actions, + nActions = this._nActiveActions, + bindings = this._bindings, + nBindings = this._nActiveBindings; + + this._nActiveActions = 0; + this._nActiveBindings = 0; + + for ( var i = 0; i !== nActions; ++ i ) { + + actions[ i ].reset(); + + } + + for ( var i = 0; i !== nBindings; ++ i ) { + + bindings[ i ].useCount = 0; + + } + + return this; + + }, + + // advance the time and update apply the animation + update: function ( deltaTime ) { + + deltaTime *= this.timeScale; + + var actions = this._actions, + nActions = this._nActiveActions, + + time = this.time += deltaTime, + timeDirection = Math.sign( deltaTime ), + + accuIndex = this._accuIndex ^= 1; + + // run active actions + + for ( var i = 0; i !== nActions; ++ i ) { + + var action = actions[ i ]; + + action._update( time, deltaTime, timeDirection, accuIndex ); + + } + + // update scene graph + + var bindings = this._bindings, + nBindings = this._nActiveBindings; + + for ( var i = 0; i !== nBindings; ++ i ) { + + bindings[ i ].apply( accuIndex ); + + } + + return this; + + }, + + // return this mixer's root target object + getRoot: function () { + + return this._root; + + }, + + // free all resources specific to a particular clip + uncacheClip: function ( clip ) { + + var actions = this._actions, + clipUuid = clip.uuid, + actionsByClip = this._actionsByClip, + actionsForClip = actionsByClip[ clipUuid ]; + + if ( actionsForClip !== undefined ) { + + // note: just calling _removeInactiveAction would mess up the + // iteration state and also require updating the state we can + // just throw away + + var actionsToRemove = actionsForClip.knownActions; + + for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) { + + var action = actionsToRemove[ i ]; + + this._deactivateAction( action ); + + var cacheIndex = action._cacheIndex, + lastInactiveAction = actions[ actions.length - 1 ]; + + action._cacheIndex = null; + action._byClipCacheIndex = null; + + lastInactiveAction._cacheIndex = cacheIndex; + actions[ cacheIndex ] = lastInactiveAction; + actions.pop(); + + this._removeInactiveBindingsForAction( action ); + + } + + delete actionsByClip[ clipUuid ]; + + } + + }, + + // free all resources specific to a particular root target object + uncacheRoot: function ( root ) { + + var rootUuid = root.uuid, + actionsByClip = this._actionsByClip; + + for ( var clipUuid in actionsByClip ) { + + var actionByRoot = actionsByClip[ clipUuid ].actionByRoot, + action = actionByRoot[ rootUuid ]; + + if ( action !== undefined ) { + + this._deactivateAction( action ); + this._removeInactiveAction( action ); + + } + + } + + var bindingsByRoot = this._bindingsByRootAndName, + bindingByName = bindingsByRoot[ rootUuid ]; + + if ( bindingByName !== undefined ) { + + for ( var trackName in bindingByName ) { + + var binding = bindingByName[ trackName ]; + binding.restoreOriginalState(); + this._removeInactiveBinding( binding ); + + } + + } + + }, + + // remove a targeted clip from the cache + uncacheAction: function ( clip, optionalRoot ) { + + var action = this.existingAction( clip, optionalRoot ); + + if ( action !== null ) { + + this._deactivateAction( action ); + this._removeInactiveAction( action ); + + } + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Uniform( value ) { + + if ( typeof value === 'string' ) { + + console.warn( 'THREE.Uniform: Type parameter is no longer needed.' ); + value = arguments[ 1 ]; + + } + + this.value = value; + + } + + Uniform.prototype.clone = function () { + + return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() ); + + }; + + /** + * @author benaadams / https://twitter.com/ben_a_adams + */ + + function InstancedBufferGeometry() { + + BufferGeometry.call( this ); + + this.type = 'InstancedBufferGeometry'; + this.maxInstancedCount = undefined; + + } + + InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), { + + constructor: InstancedBufferGeometry, + + isInstancedBufferGeometry: true, + + copy: function ( source ) { + + BufferGeometry.prototype.copy.call( this, source ); + + this.maxInstancedCount = source.maxInstancedCount; + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + } + + } ); + + /** + * @author benaadams / https://twitter.com/ben_a_adams + */ + + function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) { + + this.uuid = _Math.generateUUID(); + + this.data = interleavedBuffer; + this.itemSize = itemSize; + this.offset = offset; + + this.normalized = normalized === true; + + } + + Object.defineProperties( InterleavedBufferAttribute.prototype, { + + count: { + + get: function () { + + return this.data.count; + + } + + }, + + array: { + + get: function () { + + return this.data.array; + + } + + } + + } ); + + Object.assign( InterleavedBufferAttribute.prototype, { + + isInterleavedBufferAttribute: true, + + setX: function ( index, x ) { + + this.data.array[ index * this.data.stride + this.offset ] = x; + + return this; + + }, + + setY: function ( index, y ) { + + this.data.array[ index * this.data.stride + this.offset + 1 ] = y; + + return this; + + }, + + setZ: function ( index, z ) { + + this.data.array[ index * this.data.stride + this.offset + 2 ] = z; + + return this; + + }, + + setW: function ( index, w ) { + + this.data.array[ index * this.data.stride + this.offset + 3 ] = w; + + return this; + + }, + + getX: function ( index ) { + + return this.data.array[ index * this.data.stride + this.offset ]; + + }, + + getY: function ( index ) { + + return this.data.array[ index * this.data.stride + this.offset + 1 ]; + + }, + + getZ: function ( index ) { + + return this.data.array[ index * this.data.stride + this.offset + 2 ]; + + }, + + getW: function ( index ) { + + return this.data.array[ index * this.data.stride + this.offset + 3 ]; + + }, + + setXY: function ( index, x, y ) { + + index = index * this.data.stride + this.offset; + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + + return this; + + }, + + setXYZ: function ( index, x, y, z ) { + + index = index * this.data.stride + this.offset; + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + + return this; + + }, + + setXYZW: function ( index, x, y, z, w ) { + + index = index * this.data.stride + this.offset; + + this.data.array[ index + 0 ] = x; + this.data.array[ index + 1 ] = y; + this.data.array[ index + 2 ] = z; + this.data.array[ index + 3 ] = w; + + return this; + + } + + } ); + + /** + * @author benaadams / https://twitter.com/ben_a_adams + */ + + function InterleavedBuffer( array, stride ) { + + this.uuid = _Math.generateUUID(); + + this.array = array; + this.stride = stride; + this.count = array !== undefined ? array.length / stride : 0; + + this.dynamic = false; + this.updateRange = { offset: 0, count: - 1 }; + + this.onUploadCallback = function () {}; + + this.version = 0; + + } + + Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', { + + set: function ( value ) { + + if ( value === true ) this.version ++; + + } + + } ); + + Object.assign( InterleavedBuffer.prototype, { + + isInterleavedBuffer: true, + + setArray: function ( array ) { + + if ( Array.isArray( array ) ) { + + throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' ); + + } + + this.count = array !== undefined ? array.length / this.stride : 0; + this.array = array; + + }, + + setDynamic: function ( value ) { + + this.dynamic = value; + + return this; + + }, + + copy: function ( source ) { + + this.array = new source.array.constructor( source.array ); + this.count = source.count; + this.stride = source.stride; + this.dynamic = source.dynamic; + + return this; + + }, + + copyAt: function ( index1, attribute, index2 ) { + + index1 *= this.stride; + index2 *= attribute.stride; + + for ( var i = 0, l = this.stride; i < l; i ++ ) { + + this.array[ index1 + i ] = attribute.array[ index2 + i ]; + + } + + return this; + + }, + + set: function ( value, offset ) { + + if ( offset === undefined ) offset = 0; + + this.array.set( value, offset ); + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + onUpload: function ( callback ) { + + this.onUploadCallback = callback; + + return this; + + } + + } ); + + /** + * @author benaadams / https://twitter.com/ben_a_adams + */ + + function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) { + + InterleavedBuffer.call( this, array, stride ); + + this.meshPerAttribute = meshPerAttribute || 1; + + } + + InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), { + + constructor: InstancedInterleavedBuffer, + + isInstancedInterleavedBuffer: true, + + copy: function ( source ) { + + InterleavedBuffer.prototype.copy.call( this, source ); + + this.meshPerAttribute = source.meshPerAttribute; + + return this; + + } + + } ); + + /** + * @author benaadams / https://twitter.com/ben_a_adams + */ + + function InstancedBufferAttribute( array, itemSize, meshPerAttribute ) { + + BufferAttribute.call( this, array, itemSize ); + + this.meshPerAttribute = meshPerAttribute || 1; + + } + + InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), { + + constructor: InstancedBufferAttribute, + + isInstancedBufferAttribute: true, + + copy: function ( source ) { + + BufferAttribute.prototype.copy.call( this, source ); + + this.meshPerAttribute = source.meshPerAttribute; + + return this; + + } + + } ); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author bhouston / http://clara.io/ + * @author stephomi / http://stephaneginier.com/ + */ + + function Raycaster( origin, direction, near, far ) { + + this.ray = new Ray( origin, direction ); + // direction is assumed to be normalized (for accurate distance calculations) + + this.near = near || 0; + this.far = far || Infinity; + + this.params = { + Mesh: {}, + Line: {}, + LOD: {}, + Points: { threshold: 1 }, + Sprite: {} + }; + + Object.defineProperties( this.params, { + PointCloud: { + get: function () { + + console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' ); + return this.Points; + + } + } + } ); + + } + + function ascSort( a, b ) { + + return a.distance - b.distance; + + } + + function intersectObject( object, raycaster, intersects, recursive ) { + + if ( object.visible === false ) return; + + object.raycast( raycaster, intersects ); + + if ( recursive === true ) { + + var children = object.children; + + for ( var i = 0, l = children.length; i < l; i ++ ) { + + intersectObject( children[ i ], raycaster, intersects, true ); + + } + + } + + } + + Object.assign( Raycaster.prototype, { + + linePrecision: 1, + + set: function ( origin, direction ) { + + // direction is assumed to be normalized (for accurate distance calculations) + + this.ray.set( origin, direction ); + + }, + + setFromCamera: function ( coords, camera ) { + + if ( ( camera && camera.isPerspectiveCamera ) ) { + + this.ray.origin.setFromMatrixPosition( camera.matrixWorld ); + this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize(); + + } else if ( ( camera && camera.isOrthographicCamera ) ) { + + this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera + this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld ); + + } else { + + console.error( 'THREE.Raycaster: Unsupported camera type.' ); + + } + + }, + + intersectObject: function ( object, recursive ) { + + var intersects = []; + + intersectObject( object, this, intersects, recursive ); + + intersects.sort( ascSort ); + + return intersects; + + }, + + intersectObjects: function ( objects, recursive ) { + + var intersects = []; + + if ( Array.isArray( objects ) === false ) { + + console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' ); + return intersects; + + } + + for ( var i = 0, l = objects.length; i < l; i ++ ) { + + intersectObject( objects[ i ], this, intersects, recursive ); + + } + + intersects.sort( ascSort ); + + return intersects; + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function Clock( autoStart ) { + + this.autoStart = ( autoStart !== undefined ) ? autoStart : true; + + this.startTime = 0; + this.oldTime = 0; + this.elapsedTime = 0; + + this.running = false; + + } + + Object.assign( Clock.prototype, { + + start: function () { + + this.startTime = ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732 + + this.oldTime = this.startTime; + this.elapsedTime = 0; + this.running = true; + + }, + + stop: function () { + + this.getElapsedTime(); + this.running = false; + this.autoStart = false; + + }, + + getElapsedTime: function () { + + this.getDelta(); + return this.elapsedTime; + + }, + + getDelta: function () { + + var diff = 0; + + if ( this.autoStart && ! this.running ) { + + this.start(); + return 0; + + } + + if ( this.running ) { + + var newTime = ( typeof performance === 'undefined' ? Date : performance ).now(); + + diff = ( newTime - this.oldTime ) / 1000; + this.oldTime = newTime; + + this.elapsedTime += diff; + + } + + return diff; + + } + + } ); + + /** + * @author bhouston / http://clara.io + * @author WestLangley / http://github.com/WestLangley + * + * Ref: https://en.wikipedia.org/wiki/Spherical_coordinate_system + * + * The poles (phi) are at the positive and negative y axis. + * The equator starts at positive z. + */ + + function Spherical( radius, phi, theta ) { + + this.radius = ( radius !== undefined ) ? radius : 1.0; + this.phi = ( phi !== undefined ) ? phi : 0; // up / down towards top and bottom pole + this.theta = ( theta !== undefined ) ? theta : 0; // around the equator of the sphere + + return this; + + } + + Object.assign( Spherical.prototype, { + + set: function ( radius, phi, theta ) { + + this.radius = radius; + this.phi = phi; + this.theta = theta; + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( other ) { + + this.radius = other.radius; + this.phi = other.phi; + this.theta = other.theta; + + return this; + + }, + + // restrict phi to be betwee EPS and PI-EPS + makeSafe: function () { + + var EPS = 0.000001; + this.phi = Math.max( EPS, Math.min( Math.PI - EPS, this.phi ) ); + + return this; + + }, + + setFromVector3: function ( vec3 ) { + + this.radius = vec3.length(); + + if ( this.radius === 0 ) { + + this.theta = 0; + this.phi = 0; + + } else { + + this.theta = Math.atan2( vec3.x, vec3.z ); // equator angle around y-up axis + this.phi = Math.acos( _Math.clamp( vec3.y / this.radius, - 1, 1 ) ); // polar angle + + } + + return this; + + } + + } ); + + /** + * @author Mugen87 / https://github.com/Mugen87 + * + * Ref: https://en.wikipedia.org/wiki/Cylindrical_coordinate_system + * + */ + + function Cylindrical( radius, theta, y ) { + + this.radius = ( radius !== undefined ) ? radius : 1.0; // distance from the origin to a point in the x-z plane + this.theta = ( theta !== undefined ) ? theta : 0; // counterclockwise angle in the x-z plane measured in radians from the positive z-axis + this.y = ( y !== undefined ) ? y : 0; // height above the x-z plane + + return this; + + } + + Object.assign( Cylindrical.prototype, { + + set: function ( radius, theta, y ) { + + this.radius = radius; + this.theta = theta; + this.y = y; + + return this; + + }, + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( other ) { + + this.radius = other.radius; + this.theta = other.theta; + this.y = other.y; + + return this; + + }, + + setFromVector3: function ( vec3 ) { + + this.radius = Math.sqrt( vec3.x * vec3.x + vec3.z * vec3.z ); + this.theta = Math.atan2( vec3.x, vec3.z ); + this.y = vec3.y; + + return this; + + } + + } ); + + /** + * @author bhouston / http://clara.io + */ + + function Box2( min, max ) { + + this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity ); + this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity ); + + } + + Object.assign( Box2.prototype, { + + set: function ( min, max ) { + + this.min.copy( min ); + this.max.copy( max ); + + return this; + + }, + + setFromPoints: function ( points ) { + + this.makeEmpty(); + + for ( var i = 0, il = points.length; i < il; i ++ ) { + + this.expandByPoint( points[ i ] ); + + } + + return this; + + }, + + setFromCenterAndSize: function () { + + var v1 = new Vector2(); + + return function setFromCenterAndSize( center, size ) { + + var halfSize = v1.copy( size ).multiplyScalar( 0.5 ); + this.min.copy( center ).sub( halfSize ); + this.max.copy( center ).add( halfSize ); + + return this; + + }; + + }(), + + clone: function () { + + return new this.constructor().copy( this ); + + }, + + copy: function ( box ) { + + this.min.copy( box.min ); + this.max.copy( box.max ); + + return this; + + }, + + makeEmpty: function () { + + this.min.x = this.min.y = + Infinity; + this.max.x = this.max.y = - Infinity; + + return this; + + }, + + isEmpty: function () { + + // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes + + return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ); + + }, + + getCenter: function ( optionalTarget ) { + + var result = optionalTarget || new Vector2(); + return this.isEmpty() ? result.set( 0, 0 ) : result.addVectors( this.min, this.max ).multiplyScalar( 0.5 ); + + }, + + getSize: function ( optionalTarget ) { + + var result = optionalTarget || new Vector2(); + return this.isEmpty() ? result.set( 0, 0 ) : result.subVectors( this.max, this.min ); + + }, + + expandByPoint: function ( point ) { + + this.min.min( point ); + this.max.max( point ); + + return this; + + }, + + expandByVector: function ( vector ) { + + this.min.sub( vector ); + this.max.add( vector ); + + return this; + + }, + + expandByScalar: function ( scalar ) { + + this.min.addScalar( - scalar ); + this.max.addScalar( scalar ); + + return this; + + }, + + containsPoint: function ( point ) { + + return point.x < this.min.x || point.x > this.max.x || + point.y < this.min.y || point.y > this.max.y ? false : true; + + }, + + containsBox: function ( box ) { + + return this.min.x <= box.min.x && box.max.x <= this.max.x && + this.min.y <= box.min.y && box.max.y <= this.max.y; + + }, + + getParameter: function ( point, optionalTarget ) { + + // This can potentially have a divide by zero if the box + // has a size dimension of 0. + + var result = optionalTarget || new Vector2(); + + return result.set( + ( point.x - this.min.x ) / ( this.max.x - this.min.x ), + ( point.y - this.min.y ) / ( this.max.y - this.min.y ) + ); + + }, + + intersectsBox: function ( box ) { + + // using 4 splitting planes to rule out intersections + + return box.max.x < this.min.x || box.min.x > this.max.x || + box.max.y < this.min.y || box.min.y > this.max.y ? false : true; + + }, + + clampPoint: function ( point, optionalTarget ) { + + var result = optionalTarget || new Vector2(); + return result.copy( point ).clamp( this.min, this.max ); + + }, + + distanceToPoint: function () { + + var v1 = new Vector2(); + + return function distanceToPoint( point ) { + + var clampedPoint = v1.copy( point ).clamp( this.min, this.max ); + return clampedPoint.sub( point ).length(); + + }; + + }(), + + intersect: function ( box ) { + + this.min.max( box.min ); + this.max.min( box.max ); + + return this; + + }, + + union: function ( box ) { + + this.min.min( box.min ); + this.max.max( box.max ); + + return this; + + }, + + translate: function ( offset ) { + + this.min.add( offset ); + this.max.add( offset ); + + return this; + + }, + + equals: function ( box ) { + + return box.min.equals( this.min ) && box.max.equals( this.max ); + + } + + } ); + + /** + * @author alteredq / http://alteredqualia.com/ + */ + + function ImmediateRenderObject( material ) { + + Object3D.call( this ); + + this.material = material; + this.render = function ( /* renderCallback */ ) {}; + + } + + ImmediateRenderObject.prototype = Object.create( Object3D.prototype ); + ImmediateRenderObject.prototype.constructor = ImmediateRenderObject; + + ImmediateRenderObject.prototype.isImmediateRenderObject = true; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function VertexNormalsHelper( object, size, hex, linewidth ) { + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xff0000; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + // + + var nNormals = 0; + + var objGeometry = this.object.geometry; + + if ( objGeometry && objGeometry.isGeometry ) { + + nNormals = objGeometry.faces.length * 3; + + } else if ( objGeometry && objGeometry.isBufferGeometry ) { + + nNormals = objGeometry.attributes.normal.count; + + } + + // + + var geometry = new BufferGeometry(); + + var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 ); + + geometry.addAttribute( 'position', positions ); + + LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) ); + + // + + this.matrixAutoUpdate = false; + + this.update(); + + } + + VertexNormalsHelper.prototype = Object.create( LineSegments.prototype ); + VertexNormalsHelper.prototype.constructor = VertexNormalsHelper; + + VertexNormalsHelper.prototype.update = ( function () { + + var v1 = new Vector3(); + var v2 = new Vector3(); + var normalMatrix = new Matrix3(); + + return function update() { + + var keys = [ 'a', 'b', 'c' ]; + + this.object.updateMatrixWorld( true ); + + normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var matrixWorld = this.object.matrixWorld; + + var position = this.geometry.attributes.position; + + // + + var objGeometry = this.object.geometry; + + if ( objGeometry && objGeometry.isGeometry ) { + + var vertices = objGeometry.vertices; + + var faces = objGeometry.faces; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + var vertex = vertices[ face[ keys[ j ] ] ]; + + var normal = face.vertexNormals[ j ]; + + v1.copy( vertex ).applyMatrix4( matrixWorld ); + + v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); + + position.setXYZ( idx, v1.x, v1.y, v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, v2.x, v2.y, v2.z ); + + idx = idx + 1; + + } + + } + + } else if ( objGeometry && objGeometry.isBufferGeometry ) { + + var objPos = objGeometry.attributes.position; + + var objNorm = objGeometry.attributes.normal; + + var idx = 0; + + // for simplicity, ignore index and drawcalls, and render every normal + + for ( var j = 0, jl = objPos.count; j < jl; j ++ ) { + + v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld ); + + v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) ); + + v2.applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); + + position.setXYZ( idx, v1.x, v1.y, v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, v2.x, v2.y, v2.z ); + + idx = idx + 1; + + } + + } + + position.needsUpdate = true; + + }; + + }() ); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function SpotLightHelper( light, color ) { + + Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.color = color; + + var geometry = new BufferGeometry(); + + var positions = [ + 0, 0, 0, 0, 0, 1, + 0, 0, 0, 1, 0, 1, + 0, 0, 0, - 1, 0, 1, + 0, 0, 0, 0, 1, 1, + 0, 0, 0, 0, - 1, 1 + ]; + + for ( var i = 0, j = 1, l = 32; i < l; i ++, j ++ ) { + + var p1 = ( i / l ) * Math.PI * 2; + var p2 = ( j / l ) * Math.PI * 2; + + positions.push( + Math.cos( p1 ), Math.sin( p1 ), 1, + Math.cos( p2 ), Math.sin( p2 ), 1 + ); + + } + + geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + + var material = new LineBasicMaterial( { fog: false } ); + + this.cone = new LineSegments( geometry, material ); + this.add( this.cone ); + + this.update(); + + } + + SpotLightHelper.prototype = Object.create( Object3D.prototype ); + SpotLightHelper.prototype.constructor = SpotLightHelper; + + SpotLightHelper.prototype.dispose = function () { + + this.cone.geometry.dispose(); + this.cone.material.dispose(); + + }; + + SpotLightHelper.prototype.update = function () { + + var vector = new Vector3(); + var vector2 = new Vector3(); + + return function update() { + + this.light.updateMatrixWorld(); + + var coneLength = this.light.distance ? this.light.distance : 1000; + var coneWidth = coneLength * Math.tan( this.light.angle ); + + this.cone.scale.set( coneWidth, coneWidth, coneLength ); + + vector.setFromMatrixPosition( this.light.matrixWorld ); + vector2.setFromMatrixPosition( this.light.target.matrixWorld ); + + this.cone.lookAt( vector2.sub( vector ) ); + + if ( this.color !== undefined ) { + + this.cone.material.color.set( this.color ); + + } else { + + this.cone.material.color.copy( this.light.color ); + + } + + }; + + }(); + + /** + * @author Sean Griffin / http://twitter.com/sgrif + * @author Michael Guerrero / http://realitymeltdown.com + * @author mrdoob / http://mrdoob.com/ + * @author ikerr / http://verold.com + * @author Mugen87 / https://github.com/Mugen87 + */ + + function getBoneList( object ) { + + var boneList = []; + + if ( object && object.isBone ) { + + boneList.push( object ); + + } + + for ( var i = 0; i < object.children.length; i ++ ) { + + boneList.push.apply( boneList, getBoneList( object.children[ i ] ) ); + + } + + return boneList; + + } + + function SkeletonHelper( object ) { + + var bones = getBoneList( object ); + + var geometry = new BufferGeometry(); + + var vertices = []; + var colors = []; + + var color1 = new Color( 0, 0, 1 ); + var color2 = new Color( 0, 1, 0 ); + + for ( var i = 0; i < bones.length; i ++ ) { + + var bone = bones[ i ]; + + if ( bone.parent && bone.parent.isBone ) { + + vertices.push( 0, 0, 0 ); + vertices.push( 0, 0, 0 ); + colors.push( color1.r, color1.g, color1.b ); + colors.push( color2.r, color2.g, color2.b ); + + } + + } + + geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + var material = new LineBasicMaterial( { vertexColors: VertexColors, depthTest: false, depthWrite: false, transparent: true } ); + + LineSegments.call( this, geometry, material ); + + this.root = object; + this.bones = bones; + + this.matrix = object.matrixWorld; + this.matrixAutoUpdate = false; + + } + + SkeletonHelper.prototype = Object.create( LineSegments.prototype ); + SkeletonHelper.prototype.constructor = SkeletonHelper; + + SkeletonHelper.prototype.updateMatrixWorld = function () { + + var vector = new Vector3(); + + var boneMatrix = new Matrix4(); + var matrixWorldInv = new Matrix4(); + + return function updateMatrixWorld( force ) { + + var bones = this.bones; + + var geometry = this.geometry; + var position = geometry.getAttribute( 'position' ); + + matrixWorldInv.getInverse( this.root.matrixWorld ); + + for ( var i = 0, j = 0; i < bones.length; i ++ ) { + + var bone = bones[ i ]; + + if ( bone.parent && bone.parent.isBone ) { + + boneMatrix.multiplyMatrices( matrixWorldInv, bone.matrixWorld ); + vector.setFromMatrixPosition( boneMatrix ); + position.setXYZ( j, vector.x, vector.y, vector.z ); + + boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld ); + vector.setFromMatrixPosition( boneMatrix ); + position.setXYZ( j + 1, vector.x, vector.y, vector.z ); + + j += 2; + + } + + } + + geometry.getAttribute( 'position' ).needsUpdate = true; + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + }; + + }(); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + */ + + function PointLightHelper( light, sphereSize, color ) { + + this.light = light; + this.light.updateMatrixWorld(); + + this.color = color; + + var geometry = new SphereBufferGeometry( sphereSize, 4, 2 ); + var material = new MeshBasicMaterial( { wireframe: true, fog: false } ); + + Mesh.call( this, geometry, material ); + + this.matrix = this.light.matrixWorld; + this.matrixAutoUpdate = false; + + this.update(); + + + /* + var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 ); + var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } ); + + this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial ); + this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial ); + + var d = light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.scale.set( d, d, d ); + + } + + this.add( this.lightDistance ); + */ + + } + + PointLightHelper.prototype = Object.create( Mesh.prototype ); + PointLightHelper.prototype.constructor = PointLightHelper; + + PointLightHelper.prototype.dispose = function () { + + this.geometry.dispose(); + this.material.dispose(); + + }; + + PointLightHelper.prototype.update = function () { + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + + } else { + + this.material.color.copy( this.light.color ); + + } + + /* + var d = this.light.distance; + + if ( d === 0.0 ) { + + this.lightDistance.visible = false; + + } else { + + this.lightDistance.visible = true; + this.lightDistance.scale.set( d, d, d ); + + } + */ + + }; + + /** + * @author abelnation / http://github.com/abelnation + * @author Mugen87 / http://github.com/Mugen87 + * @author WestLangley / http://github.com/WestLangley + */ + + function RectAreaLightHelper( light, color ) { + + Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.color = color; + + var material = new LineBasicMaterial( { fog: false } ); + + var geometry = new BufferGeometry(); + + geometry.addAttribute( 'position', new BufferAttribute( new Float32Array( 5 * 3 ), 3 ) ); + + this.line = new Line( geometry, material ); + this.add( this.line ); + + + this.update(); + + } + + RectAreaLightHelper.prototype = Object.create( Object3D.prototype ); + RectAreaLightHelper.prototype.constructor = RectAreaLightHelper; + + RectAreaLightHelper.prototype.dispose = function () { + + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + }; + + RectAreaLightHelper.prototype.update = function () { + + // calculate new dimensions of the helper + + var hx = this.light.width * 0.5; + var hy = this.light.height * 0.5; + + var position = this.line.geometry.attributes.position; + var array = position.array; + + // update vertices + + array[ 0 ] = hx; array[ 1 ] = - hy; array[ 2 ] = 0; + array[ 3 ] = hx; array[ 4 ] = hy; array[ 5 ] = 0; + array[ 6 ] = - hx; array[ 7 ] = hy; array[ 8 ] = 0; + array[ 9 ] = - hx; array[ 10 ] = - hy; array[ 11 ] = 0; + array[ 12 ] = hx; array[ 13 ] = - hy; array[ 14 ] = 0; + + position.needsUpdate = true; + + if ( this.color !== undefined ) { + + this.line.material.color.set( this.color ); + + } else { + + this.line.material.color.copy( this.light.color ); + + } + + }; + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / https://github.com/Mugen87 + */ + + function HemisphereLightHelper( light, size, color ) { + + Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.color = color; + + var geometry = new OctahedronBufferGeometry( size ); + geometry.rotateY( Math.PI * 0.5 ); + + this.material = new MeshBasicMaterial( { wireframe: true, fog: false } ); + if ( this.color === undefined ) this.material.vertexColors = VertexColors; + + var position = geometry.getAttribute( 'position' ); + var colors = new Float32Array( position.count * 3 ); + + geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) ); + + this.add( new Mesh( geometry, this.material ) ); + + this.update(); + + } + + HemisphereLightHelper.prototype = Object.create( Object3D.prototype ); + HemisphereLightHelper.prototype.constructor = HemisphereLightHelper; + + HemisphereLightHelper.prototype.dispose = function () { + + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + }; + + HemisphereLightHelper.prototype.update = function () { + + var vector = new Vector3(); + + var color1 = new Color(); + var color2 = new Color(); + + return function update() { + + var mesh = this.children[ 0 ]; + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + + } else { + + var colors = mesh.geometry.getAttribute( 'color' ); + + color1.copy( this.light.color ); + color2.copy( this.light.groundColor ); + + for ( var i = 0, l = colors.count; i < l; i ++ ) { + + var color = ( i < ( l / 2 ) ) ? color1 : color2; + + colors.setXYZ( i, color.r, color.g, color.b ); + + } + + colors.needsUpdate = true; + + } + + mesh.lookAt( vector.setFromMatrixPosition( this.light.matrixWorld ).negate() ); + + }; + + }(); + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function GridHelper( size, divisions, color1, color2 ) { + + size = size || 10; + divisions = divisions || 10; + color1 = new Color( color1 !== undefined ? color1 : 0x444444 ); + color2 = new Color( color2 !== undefined ? color2 : 0x888888 ); + + var center = divisions / 2; + var step = size / divisions; + var halfSize = size / 2; + + var vertices = [], colors = []; + + for ( var i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) { + + vertices.push( - halfSize, 0, k, halfSize, 0, k ); + vertices.push( k, 0, - halfSize, k, 0, halfSize ); + + var color = i === center ? color1 : color2; + + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + color.toArray( colors, j ); j += 3; + + } + + var geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + var material = new LineBasicMaterial( { vertexColors: VertexColors } ); + + LineSegments.call( this, geometry, material ); + + } + + GridHelper.prototype = Object.create( LineSegments.prototype ); + GridHelper.prototype.constructor = GridHelper; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / http://github.com/Mugen87 + * @author Hectate / http://www.github.com/Hectate + */ + + function PolarGridHelper( radius, radials, circles, divisions, color1, color2 ) { + + radius = radius || 10; + radials = radials || 16; + circles = circles || 8; + divisions = divisions || 64; + color1 = new Color( color1 !== undefined ? color1 : 0x444444 ); + color2 = new Color( color2 !== undefined ? color2 : 0x888888 ); + + var vertices = []; + var colors = []; + + var x, z; + var v, i, j, r, color; + + // create the radials + + for ( i = 0; i <= radials; i ++ ) { + + v = ( i / radials ) * ( Math.PI * 2 ); + + x = Math.sin( v ) * radius; + z = Math.cos( v ) * radius; + + vertices.push( 0, 0, 0 ); + vertices.push( x, 0, z ); + + color = ( i & 1 ) ? color1 : color2; + + colors.push( color.r, color.g, color.b ); + colors.push( color.r, color.g, color.b ); + + } + + // create the circles + + for ( i = 0; i <= circles; i ++ ) { + + color = ( i & 1 ) ? color1 : color2; + + r = radius - ( radius / circles * i ); + + for ( j = 0; j < divisions; j ++ ) { + + // first vertex + + v = ( j / divisions ) * ( Math.PI * 2 ); + + x = Math.sin( v ) * r; + z = Math.cos( v ) * r; + + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); + + // second vertex + + v = ( ( j + 1 ) / divisions ) * ( Math.PI * 2 ); + + x = Math.sin( v ) * r; + z = Math.cos( v ) * r; + + vertices.push( x, 0, z ); + colors.push( color.r, color.g, color.b ); + + } + + } + + var geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + var material = new LineBasicMaterial( { vertexColors: VertexColors } ); + + LineSegments.call( this, geometry, material ); + + } + + PolarGridHelper.prototype = Object.create( LineSegments.prototype ); + PolarGridHelper.prototype.constructor = PolarGridHelper; + + /** + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function FaceNormalsHelper( object, size, hex, linewidth ) { + + // FaceNormalsHelper only supports THREE.Geometry + + this.object = object; + + this.size = ( size !== undefined ) ? size : 1; + + var color = ( hex !== undefined ) ? hex : 0xffff00; + + var width = ( linewidth !== undefined ) ? linewidth : 1; + + // + + var nNormals = 0; + + var objGeometry = this.object.geometry; + + if ( objGeometry && objGeometry.isGeometry ) { + + nNormals = objGeometry.faces.length; + + } else { + + console.warn( 'THREE.FaceNormalsHelper: only THREE.Geometry is supported. Use THREE.VertexNormalsHelper, instead.' ); + + } + + // + + var geometry = new BufferGeometry(); + + var positions = new Float32BufferAttribute( nNormals * 2 * 3, 3 ); + + geometry.addAttribute( 'position', positions ); + + LineSegments.call( this, geometry, new LineBasicMaterial( { color: color, linewidth: width } ) ); + + // + + this.matrixAutoUpdate = false; + this.update(); + + } + + FaceNormalsHelper.prototype = Object.create( LineSegments.prototype ); + FaceNormalsHelper.prototype.constructor = FaceNormalsHelper; + + FaceNormalsHelper.prototype.update = ( function () { + + var v1 = new Vector3(); + var v2 = new Vector3(); + var normalMatrix = new Matrix3(); + + return function update() { + + this.object.updateMatrixWorld( true ); + + normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + var matrixWorld = this.object.matrixWorld; + + var position = this.geometry.attributes.position; + + // + + var objGeometry = this.object.geometry; + + var vertices = objGeometry.vertices; + + var faces = objGeometry.faces; + + var idx = 0; + + for ( var i = 0, l = faces.length; i < l; i ++ ) { + + var face = faces[ i ]; + + var normal = face.normal; + + v1.copy( vertices[ face.a ] ) + .add( vertices[ face.b ] ) + .add( vertices[ face.c ] ) + .divideScalar( 3 ) + .applyMatrix4( matrixWorld ); + + v2.copy( normal ).applyMatrix3( normalMatrix ).normalize().multiplyScalar( this.size ).add( v1 ); + + position.setXYZ( idx, v1.x, v1.y, v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, v2.x, v2.y, v2.z ); + + idx = idx + 1; + + } + + position.needsUpdate = true; + + }; + + }() ); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author mrdoob / http://mrdoob.com/ + * @author WestLangley / http://github.com/WestLangley + */ + + function DirectionalLightHelper( light, size, color ) { + + Object3D.call( this ); + + this.light = light; + this.light.updateMatrixWorld(); + + this.matrix = light.matrixWorld; + this.matrixAutoUpdate = false; + + this.color = color; + + if ( size === undefined ) size = 1; + + var geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( [ + - size, size, 0, + size, size, 0, + size, - size, 0, + - size, - size, 0, + - size, size, 0 + ], 3 ) ); + + var material = new LineBasicMaterial( { fog: false } ); + + this.lightPlane = new Line( geometry, material ); + this.add( this.lightPlane ); + + geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 0, 1 ], 3 ) ); + + this.targetLine = new Line( geometry, material ); + this.add( this.targetLine ); + + this.update(); + + } + + DirectionalLightHelper.prototype = Object.create( Object3D.prototype ); + DirectionalLightHelper.prototype.constructor = DirectionalLightHelper; + + DirectionalLightHelper.prototype.dispose = function () { + + this.lightPlane.geometry.dispose(); + this.lightPlane.material.dispose(); + this.targetLine.geometry.dispose(); + this.targetLine.material.dispose(); + + }; + + DirectionalLightHelper.prototype.update = function () { + + var v1 = new Vector3(); + var v2 = new Vector3(); + var v3 = new Vector3(); + + return function update() { + + v1.setFromMatrixPosition( this.light.matrixWorld ); + v2.setFromMatrixPosition( this.light.target.matrixWorld ); + v3.subVectors( v2, v1 ); + + this.lightPlane.lookAt( v3 ); + + if ( this.color !== undefined ) { + + this.lightPlane.material.color.set( this.color ); + this.targetLine.material.color.set( this.color ); + + } else { + + this.lightPlane.material.color.copy( this.light.color ); + this.targetLine.material.color.copy( this.light.color ); + + } + + this.targetLine.lookAt( v3 ); + this.targetLine.scale.z = v3.length(); + + }; + + }(); + + /** + * @author alteredq / http://alteredqualia.com/ + * @author Mugen87 / https://github.com/Mugen87 + * + * - shows frustum, line of sight and up of the camera + * - suitable for fast updates + * - based on frustum visualization in lightgl.js shadowmap example + * http://evanw.github.com/lightgl.js/tests/shadowmap.html + */ + + function CameraHelper( camera ) { + + var geometry = new BufferGeometry(); + var material = new LineBasicMaterial( { color: 0xffffff, vertexColors: FaceColors } ); + + var vertices = []; + var colors = []; + + var pointMap = {}; + + // colors + + var colorFrustum = new Color( 0xffaa00 ); + var colorCone = new Color( 0xff0000 ); + var colorUp = new Color( 0x00aaff ); + var colorTarget = new Color( 0xffffff ); + var colorCross = new Color( 0x333333 ); + + // near + + addLine( 'n1', 'n2', colorFrustum ); + addLine( 'n2', 'n4', colorFrustum ); + addLine( 'n4', 'n3', colorFrustum ); + addLine( 'n3', 'n1', colorFrustum ); + + // far + + addLine( 'f1', 'f2', colorFrustum ); + addLine( 'f2', 'f4', colorFrustum ); + addLine( 'f4', 'f3', colorFrustum ); + addLine( 'f3', 'f1', colorFrustum ); + + // sides + + addLine( 'n1', 'f1', colorFrustum ); + addLine( 'n2', 'f2', colorFrustum ); + addLine( 'n3', 'f3', colorFrustum ); + addLine( 'n4', 'f4', colorFrustum ); + + // cone + + addLine( 'p', 'n1', colorCone ); + addLine( 'p', 'n2', colorCone ); + addLine( 'p', 'n3', colorCone ); + addLine( 'p', 'n4', colorCone ); + + // up + + addLine( 'u1', 'u2', colorUp ); + addLine( 'u2', 'u3', colorUp ); + addLine( 'u3', 'u1', colorUp ); + + // target + + addLine( 'c', 't', colorTarget ); + addLine( 'p', 'c', colorCross ); + + // cross + + addLine( 'cn1', 'cn2', colorCross ); + addLine( 'cn3', 'cn4', colorCross ); + + addLine( 'cf1', 'cf2', colorCross ); + addLine( 'cf3', 'cf4', colorCross ); + + function addLine( a, b, color ) { + + addPoint( a, color ); + addPoint( b, color ); + + } + + function addPoint( id, color ) { + + vertices.push( 0, 0, 0 ); + colors.push( color.r, color.g, color.b ); + + if ( pointMap[ id ] === undefined ) { + + pointMap[ id ] = []; + + } + + pointMap[ id ].push( ( vertices.length / 3 ) - 1 ); + + } + + geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + LineSegments.call( this, geometry, material ); + + this.camera = camera; + if ( this.camera.updateProjectionMatrix ) this.camera.updateProjectionMatrix(); + + this.matrix = camera.matrixWorld; + this.matrixAutoUpdate = false; + + this.pointMap = pointMap; + + this.update(); + + } + + CameraHelper.prototype = Object.create( LineSegments.prototype ); + CameraHelper.prototype.constructor = CameraHelper; + + CameraHelper.prototype.update = function () { + + var geometry, pointMap; + + var vector = new Vector3(); + var camera = new Camera(); + + function setPoint( point, x, y, z ) { + + vector.set( x, y, z ).unproject( camera ); + + var points = pointMap[ point ]; + + if ( points !== undefined ) { + + var position = geometry.getAttribute( 'position' ); + + for ( var i = 0, l = points.length; i < l; i ++ ) { + + position.setXYZ( points[ i ], vector.x, vector.y, vector.z ); + + } + + } + + } + + return function update() { + + geometry = this.geometry; + pointMap = this.pointMap; + + var w = 1, h = 1; + + // we need just camera projection matrix + // world matrix must be identity + + camera.projectionMatrix.copy( this.camera.projectionMatrix ); + + // center / target + + setPoint( 'c', 0, 0, - 1 ); + setPoint( 't', 0, 0, 1 ); + + // near + + setPoint( 'n1', - w, - h, - 1 ); + setPoint( 'n2', w, - h, - 1 ); + setPoint( 'n3', - w, h, - 1 ); + setPoint( 'n4', w, h, - 1 ); + + // far + + setPoint( 'f1', - w, - h, 1 ); + setPoint( 'f2', w, - h, 1 ); + setPoint( 'f3', - w, h, 1 ); + setPoint( 'f4', w, h, 1 ); + + // up + + setPoint( 'u1', w * 0.7, h * 1.1, - 1 ); + setPoint( 'u2', - w * 0.7, h * 1.1, - 1 ); + setPoint( 'u3', 0, h * 2, - 1 ); + + // cross + + setPoint( 'cf1', - w, 0, 1 ); + setPoint( 'cf2', w, 0, 1 ); + setPoint( 'cf3', 0, - h, 1 ); + setPoint( 'cf4', 0, h, 1 ); + + setPoint( 'cn1', - w, 0, - 1 ); + setPoint( 'cn2', w, 0, - 1 ); + setPoint( 'cn3', 0, - h, - 1 ); + setPoint( 'cn4', 0, h, - 1 ); + + geometry.getAttribute( 'position' ).needsUpdate = true; + + }; + + }(); + + /** + * @author mrdoob / http://mrdoob.com/ + * @author Mugen87 / http://github.com/Mugen87 + */ + + function BoxHelper( object, color ) { + + this.object = object; + + if ( color === undefined ) color = 0xffff00; + + var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + var positions = new Float32Array( 8 * 3 ); + + var geometry = new BufferGeometry(); + geometry.setIndex( new BufferAttribute( indices, 1 ) ); + geometry.addAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) ); + + this.matrixAutoUpdate = false; + + this.update(); + + } + + BoxHelper.prototype = Object.create( LineSegments.prototype ); + BoxHelper.prototype.constructor = BoxHelper; + + BoxHelper.prototype.update = ( function () { + + var box = new Box3(); + + return function update( object ) { + + if ( object !== undefined ) { + + console.warn( 'THREE.BoxHelper: .update() has no longer arguments.' ); + + } + + if ( this.object !== undefined ) { + + box.setFromObject( this.object ); + + } + + if ( box.isEmpty() ) return; + + var min = box.min; + var max = box.max; + + /* + 5____4 + 1/___0/| + | 6__|_7 + 2/___3/ + + 0: max.x, max.y, max.z + 1: min.x, max.y, max.z + 2: min.x, min.y, max.z + 3: max.x, min.y, max.z + 4: max.x, max.y, min.z + 5: min.x, max.y, min.z + 6: min.x, min.y, min.z + 7: max.x, min.y, min.z + */ + + var position = this.geometry.attributes.position; + var array = position.array; + + array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; + array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; + array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; + array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; + array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; + array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; + array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; + array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; + + position.needsUpdate = true; + + this.geometry.computeBoundingSphere(); + + }; + + } )(); + + BoxHelper.prototype.setFromObject = function ( object ) { + + this.object = object; + this.update(); + + return this; + + }; + + /** + * @author WestLangley / http://github.com/WestLangley + */ + + function Box3Helper( box, hex ) { + + this.type = 'Box3Helper'; + + this.box = box; + + var color = ( hex !== undefined ) ? hex : 0xffff00; + + var indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + + var positions = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 1, - 1, 1, - 1, - 1 ]; + + var geometry = new BufferGeometry(); + + geometry.setIndex( new BufferAttribute( indices, 1 ) ); + + geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + + LineSegments.call( this, geometry, new LineBasicMaterial( { color: color } ) ); + + this.geometry.computeBoundingSphere(); + + } + + Box3Helper.prototype = Object.create( LineSegments.prototype ); + Box3Helper.prototype.constructor = Box3Helper; + + Box3Helper.prototype.updateMatrixWorld = function ( force ) { + + var box = this.box; + + if ( box.isEmpty() ) return; + + box.getCenter( this.position ); + + box.getSize( this.scale ); + + this.scale.multiplyScalar( 0.5 ); + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + }; + + /** + * @author WestLangley / http://github.com/WestLangley + */ + + function PlaneHelper( plane, size, hex ) { + + this.type = 'PlaneHelper'; + + this.plane = plane; + + this.size = ( size === undefined ) ? 1 : size; + + var color = ( hex !== undefined ) ? hex : 0xffff00; + + var positions = [ 1, - 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, - 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0 ]; + + var geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); + + Line.call( this, geometry, new LineBasicMaterial( { color: color } ) ); + + // + + var positions2 = [ 1, 1, 1, - 1, 1, 1, - 1, - 1, 1, 1, 1, 1, - 1, - 1, 1, 1, - 1, 1 ]; + + var geometry2 = new BufferGeometry(); + geometry2.addAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); + + this.add( new Mesh( geometry2, new MeshBasicMaterial( { color: color, opacity: 0.2, transparent: true, depthWrite: false } ) ) ); + + } + + PlaneHelper.prototype = Object.create( Line.prototype ); + PlaneHelper.prototype.constructor = PlaneHelper; + + PlaneHelper.prototype.updateMatrixWorld = function ( force ) { + + var scale = - this.plane.constant; + + if ( Math.abs( scale ) < 1e-8 ) scale = 1e-8; // sign does not matter + + this.scale.set( 0.5 * this.size, 0.5 * this.size, scale ); + + this.lookAt( this.plane.normal ); + + Object3D.prototype.updateMatrixWorld.call( this, force ); + + }; + + /** + * @author WestLangley / http://github.com/WestLangley + * @author zz85 / http://github.com/zz85 + * @author bhouston / http://clara.io + * + * Creates an arrow for visualizing directions + * + * Parameters: + * dir - Vector3 + * origin - Vector3 + * length - Number + * color - color in hex value + * headLength - Number + * headWidth - Number + */ + + var lineGeometry; + var coneGeometry; + + function ArrowHelper( dir, origin, length, color, headLength, headWidth ) { + + // dir is assumed to be normalized + + Object3D.call( this ); + + if ( color === undefined ) color = 0xffff00; + if ( length === undefined ) length = 1; + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + if ( lineGeometry === undefined ) { + + lineGeometry = new BufferGeometry(); + lineGeometry.addAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 0, 1, 0 ], 3 ) ); + + coneGeometry = new CylinderBufferGeometry( 0, 0.5, 1, 5, 1 ); + coneGeometry.translate( 0, - 0.5, 0 ); + + } + + this.position.copy( origin ); + + this.line = new Line( lineGeometry, new LineBasicMaterial( { color: color } ) ); + this.line.matrixAutoUpdate = false; + this.add( this.line ); + + this.cone = new Mesh( coneGeometry, new MeshBasicMaterial( { color: color } ) ); + this.cone.matrixAutoUpdate = false; + this.add( this.cone ); + + this.setDirection( dir ); + this.setLength( length, headLength, headWidth ); + + } + + ArrowHelper.prototype = Object.create( Object3D.prototype ); + ArrowHelper.prototype.constructor = ArrowHelper; + + ArrowHelper.prototype.setDirection = ( function () { + + var axis = new Vector3(); + var radians; + + return function setDirection( dir ) { + + // dir is assumed to be normalized + + if ( dir.y > 0.99999 ) { + + this.quaternion.set( 0, 0, 0, 1 ); + + } else if ( dir.y < - 0.99999 ) { + + this.quaternion.set( 1, 0, 0, 0 ); + + } else { + + axis.set( dir.z, 0, - dir.x ).normalize(); + + radians = Math.acos( dir.y ); + + this.quaternion.setFromAxisAngle( axis, radians ); + + } + + }; + + }() ); + + ArrowHelper.prototype.setLength = function ( length, headLength, headWidth ) { + + if ( headLength === undefined ) headLength = 0.2 * length; + if ( headWidth === undefined ) headWidth = 0.2 * headLength; + + this.line.scale.set( 1, Math.max( 0, length - headLength ), 1 ); + this.line.updateMatrix(); + + this.cone.scale.set( headWidth, headLength, headWidth ); + this.cone.position.y = length; + this.cone.updateMatrix(); + + }; + + ArrowHelper.prototype.setColor = function ( color ) { + + this.line.material.color.copy( color ); + this.cone.material.color.copy( color ); + + }; + + /** + * @author sroucheray / http://sroucheray.org/ + * @author mrdoob / http://mrdoob.com/ + */ + + function AxesHelper( size ) { + + size = size || 1; + + var vertices = [ + 0, 0, 0, size, 0, 0, + 0, 0, 0, 0, size, 0, + 0, 0, 0, 0, 0, size + ]; + + var colors = [ + 1, 0, 0, 1, 0.6, 0, + 0, 1, 0, 0.6, 1, 0, + 0, 0, 1, 0, 0.6, 1 + ]; + + var geometry = new BufferGeometry(); + geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) ); + + var material = new LineBasicMaterial( { vertexColors: VertexColors } ); + + LineSegments.call( this, geometry, material ); + + } + + AxesHelper.prototype = Object.create( LineSegments.prototype ); + AxesHelper.prototype.constructor = AxesHelper; + + /** + * @author mrdoob / http://mrdoob.com/ + */ + + function Face4( a, b, c, d, normal, color, materialIndex ) { + + console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.' ); + return new Face3( a, b, c, normal, color, materialIndex ); + + } + + var LineStrip = 0; + + var LinePieces = 1; + + function MeshFaceMaterial( materials ) { + + console.warn( 'THREE.MeshFaceMaterial has been removed. Use an Array instead.' ); + return materials; + + } + + function MultiMaterial( materials ) { + + if ( materials === undefined ) materials = []; + + console.warn( 'THREE.MultiMaterial has been removed. Use an Array instead.' ); + materials.isMultiMaterial = true; + materials.materials = materials; + materials.clone = function () { + + return materials.slice(); + + }; + return materials; + + } + + function PointCloud( geometry, material ) { + + console.warn( 'THREE.PointCloud has been renamed to THREE.Points.' ); + return new Points( geometry, material ); + + } + + function Particle( material ) { + + console.warn( 'THREE.Particle has been renamed to THREE.Sprite.' ); + return new Sprite( material ); + + } + + function ParticleSystem( geometry, material ) { + + console.warn( 'THREE.ParticleSystem has been renamed to THREE.Points.' ); + return new Points( geometry, material ); + + } + + function PointCloudMaterial( parameters ) { + + console.warn( 'THREE.PointCloudMaterial has been renamed to THREE.PointsMaterial.' ); + return new PointsMaterial( parameters ); + + } + + function ParticleBasicMaterial( parameters ) { + + console.warn( 'THREE.ParticleBasicMaterial has been renamed to THREE.PointsMaterial.' ); + return new PointsMaterial( parameters ); + + } + + function ParticleSystemMaterial( parameters ) { + + console.warn( 'THREE.ParticleSystemMaterial has been renamed to THREE.PointsMaterial.' ); + return new PointsMaterial( parameters ); + + } + + function Vertex( x, y, z ) { + + console.warn( 'THREE.Vertex has been removed. Use THREE.Vector3 instead.' ); + return new Vector3( x, y, z ); + + } + + // + + function DynamicBufferAttribute( array, itemSize ) { + + console.warn( 'THREE.DynamicBufferAttribute has been removed. Use new THREE.BufferAttribute().setDynamic( true ) instead.' ); + return new BufferAttribute( array, itemSize ).setDynamic( true ); + + } + + function Int8Attribute( array, itemSize ) { + + console.warn( 'THREE.Int8Attribute has been removed. Use new THREE.Int8BufferAttribute() instead.' ); + return new Int8BufferAttribute( array, itemSize ); + + } + + function Uint8Attribute( array, itemSize ) { + + console.warn( 'THREE.Uint8Attribute has been removed. Use new THREE.Uint8BufferAttribute() instead.' ); + return new Uint8BufferAttribute( array, itemSize ); + + } + + function Uint8ClampedAttribute( array, itemSize ) { + + console.warn( 'THREE.Uint8ClampedAttribute has been removed. Use new THREE.Uint8ClampedBufferAttribute() instead.' ); + return new Uint8ClampedBufferAttribute( array, itemSize ); + + } + + function Int16Attribute( array, itemSize ) { + + console.warn( 'THREE.Int16Attribute has been removed. Use new THREE.Int16BufferAttribute() instead.' ); + return new Int16BufferAttribute( array, itemSize ); + + } + + function Uint16Attribute( array, itemSize ) { + + console.warn( 'THREE.Uint16Attribute has been removed. Use new THREE.Uint16BufferAttribute() instead.' ); + return new Uint16BufferAttribute( array, itemSize ); + + } + + function Int32Attribute( array, itemSize ) { + + console.warn( 'THREE.Int32Attribute has been removed. Use new THREE.Int32BufferAttribute() instead.' ); + return new Int32BufferAttribute( array, itemSize ); + + } + + function Uint32Attribute( array, itemSize ) { + + console.warn( 'THREE.Uint32Attribute has been removed. Use new THREE.Uint32BufferAttribute() instead.' ); + return new Uint32BufferAttribute( array, itemSize ); + + } + + function Float32Attribute( array, itemSize ) { + + console.warn( 'THREE.Float32Attribute has been removed. Use new THREE.Float32BufferAttribute() instead.' ); + return new Float32BufferAttribute( array, itemSize ); + + } + + function Float64Attribute( array, itemSize ) { + + console.warn( 'THREE.Float64Attribute has been removed. Use new THREE.Float64BufferAttribute() instead.' ); + return new Float64BufferAttribute( array, itemSize ); + + } + + // + + Curve.create = function ( construct, getPoint ) { + + console.log( 'THREE.Curve.create() has been deprecated' ); + + construct.prototype = Object.create( Curve.prototype ); + construct.prototype.constructor = construct; + construct.prototype.getPoint = getPoint; + + return construct; + + }; + + // + + Object.assign( CurvePath.prototype, { + + createPointsGeometry: function ( divisions ) { + + console.warn( 'THREE.CurvePath: .createPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); + + // generate geometry from path points (for Line or Points objects) + + var pts = this.getPoints( divisions ); + return this.createGeometry( pts ); + + }, + + createSpacedPointsGeometry: function ( divisions ) { + + console.warn( 'THREE.CurvePath: .createSpacedPointsGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); + + // generate geometry from equidistant sampling along the path + + var pts = this.getSpacedPoints( divisions ); + return this.createGeometry( pts ); + + }, + + createGeometry: function ( points ) { + + console.warn( 'THREE.CurvePath: .createGeometry() has been removed. Use new THREE.Geometry().setFromPoints( points ) instead.' ); + + var geometry = new Geometry(); + + for ( var i = 0, l = points.length; i < l; i ++ ) { + + var point = points[ i ]; + geometry.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); + + } + + return geometry; + + } + + } ); + + // + + Object.assign( Path.prototype, { + + fromPoints: function ( points ) { + + console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' ); + this.setFromPoints( points ); + + } + + } ); + + // + + function ClosedSplineCurve3( points ) { + + console.warn( 'THREE.ClosedSplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' ); + + CatmullRomCurve3.call( this, points ); + this.type = 'catmullrom'; + this.closed = true; + + } + + ClosedSplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype ); + + // + + function SplineCurve3( points ) { + + console.warn( 'THREE.SplineCurve3 has been deprecated. Use THREE.CatmullRomCurve3 instead.' ); + + CatmullRomCurve3.call( this, points ); + this.type = 'catmullrom'; + + } + + SplineCurve3.prototype = Object.create( CatmullRomCurve3.prototype ); + + // + + function Spline( points ) { + + console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' ); + + CatmullRomCurve3.call( this, points ); + this.type = 'catmullrom'; + + } + + Spline.prototype = Object.create( CatmullRomCurve3.prototype ); + + Object.assign( Spline.prototype, { + + initFromArray: function ( /* a */ ) { + + console.error( 'THREE.Spline: .initFromArray() has been removed.' ); + + }, + getControlPointsArray: function ( /* optionalTarget */ ) { + + console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' ); + + }, + reparametrizeByArcLength: function ( /* samplingCoef */ ) { + + console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' ); + + } + + } ); + + // + + function AxisHelper( size ) { + + console.warn( 'THREE.AxisHelper has been renamed to THREE.AxesHelper.' ); + return new AxesHelper( size ); + + } + + function BoundingBoxHelper( object, color ) { + + console.warn( 'THREE.BoundingBoxHelper has been deprecated. Creating a THREE.BoxHelper instead.' ); + return new BoxHelper( object, color ); + + } + + function EdgesHelper( object, hex ) { + + console.warn( 'THREE.EdgesHelper has been removed. Use THREE.EdgesGeometry instead.' ); + return new LineSegments( new EdgesGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) ); + + } + + GridHelper.prototype.setColors = function () { + + console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' ); + + }; + + SkeletonHelper.prototype.update = function () { + + console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' ); + + }; + + function WireframeHelper( object, hex ) { + + console.warn( 'THREE.WireframeHelper has been removed. Use THREE.WireframeGeometry instead.' ); + return new LineSegments( new WireframeGeometry( object.geometry ), new LineBasicMaterial( { color: hex !== undefined ? hex : 0xffffff } ) ); + + } + + // + + Object.assign( Loader.prototype, { + + extractUrlBase: function ( url ) { + + console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' ); + return LoaderUtils.extractUrlBase( url ); + + } + + } ); + + function XHRLoader( manager ) { + + console.warn( 'THREE.XHRLoader has been renamed to THREE.FileLoader.' ); + return new FileLoader( manager ); + + } + + function BinaryTextureLoader( manager ) { + + console.warn( 'THREE.BinaryTextureLoader has been renamed to THREE.DataTextureLoader.' ); + return new DataTextureLoader( manager ); + + } + + // + + Object.assign( Box2.prototype, { + + center: function ( optionalTarget ) { + + console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' ); + return this.getCenter( optionalTarget ); + + }, + empty: function () { + + console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' ); + return this.isEmpty(); + + }, + isIntersectionBox: function ( box ) { + + console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' ); + return this.intersectsBox( box ); + + }, + size: function ( optionalTarget ) { + + console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' ); + return this.getSize( optionalTarget ); + + } + } ); + + Object.assign( Box3.prototype, { + + center: function ( optionalTarget ) { + + console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' ); + return this.getCenter( optionalTarget ); + + }, + empty: function () { + + console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' ); + return this.isEmpty(); + + }, + isIntersectionBox: function ( box ) { + + console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' ); + return this.intersectsBox( box ); + + }, + isIntersectionSphere: function ( sphere ) { + + console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' ); + return this.intersectsSphere( sphere ); + + }, + size: function ( optionalTarget ) { + + console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' ); + return this.getSize( optionalTarget ); + + } + } ); + + Line3.prototype.center = function ( optionalTarget ) { + + console.warn( 'THREE.Line3: .center() has been renamed to .getCenter().' ); + return this.getCenter( optionalTarget ); + + }; + + Object.assign( _Math, { + + random16: function () { + + console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' ); + return Math.random(); + + }, + + nearestPowerOfTwo: function ( value ) { + + console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' ); + return _Math.floorPowerOfTwo( value ); + + }, + + nextPowerOfTwo: function ( value ) { + + console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' ); + return _Math.ceilPowerOfTwo( value ); + + } + + } ); + + Object.assign( Matrix3.prototype, { + + flattenToArrayOffset: function ( array, offset ) { + + console.warn( "THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead." ); + return this.toArray( array, offset ); + + }, + multiplyVector3: function ( vector ) { + + console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' ); + return vector.applyMatrix3( this ); + + }, + multiplyVector3Array: function ( /* a */ ) { + + console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' ); + + }, + applyToBuffer: function ( buffer /*, offset, length */ ) { + + console.warn( 'THREE.Matrix3: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' ); + return this.applyToBufferAttribute( buffer ); + + }, + applyToVector3Array: function ( /* array, offset, length */ ) { + + console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' ); + + } + + } ); + + Object.assign( Matrix4.prototype, { + + extractPosition: function ( m ) { + + console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' ); + return this.copyPosition( m ); + + }, + flattenToArrayOffset: function ( array, offset ) { + + console.warn( "THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead." ); + return this.toArray( array, offset ); + + }, + getPosition: function () { + + var v1; + + return function getPosition() { + + if ( v1 === undefined ) v1 = new Vector3(); + console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' ); + return v1.setFromMatrixColumn( this, 3 ); + + }; + + }(), + setRotationFromQuaternion: function ( q ) { + + console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' ); + return this.makeRotationFromQuaternion( q ); + + }, + multiplyToArray: function () { + + console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' ); + + }, + multiplyVector3: function ( vector ) { + + console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + multiplyVector4: function ( vector ) { + + console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + multiplyVector3Array: function ( /* a */ ) { + + console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' ); + + }, + rotateAxis: function ( v ) { + + console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' ); + v.transformDirection( this ); + + }, + crossVector: function ( vector ) { + + console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' ); + return vector.applyMatrix4( this ); + + }, + translate: function () { + + console.error( 'THREE.Matrix4: .translate() has been removed.' ); + + }, + rotateX: function () { + + console.error( 'THREE.Matrix4: .rotateX() has been removed.' ); + + }, + rotateY: function () { + + console.error( 'THREE.Matrix4: .rotateY() has been removed.' ); + + }, + rotateZ: function () { + + console.error( 'THREE.Matrix4: .rotateZ() has been removed.' ); + + }, + rotateByAxis: function () { + + console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' ); + + }, + applyToBuffer: function ( buffer /*, offset, length */ ) { + + console.warn( 'THREE.Matrix4: .applyToBuffer() has been removed. Use matrix.applyToBufferAttribute( attribute ) instead.' ); + return this.applyToBufferAttribute( buffer ); + + }, + applyToVector3Array: function ( /* array, offset, length */ ) { + + console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' ); + + }, + makeFrustum: function ( left, right, bottom, top, near, far ) { + + console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' ); + return this.makePerspective( left, right, top, bottom, near, far ); + + } + + } ); + + Plane.prototype.isIntersectionLine = function ( line ) { + + console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' ); + return this.intersectsLine( line ); + + }; + + Quaternion.prototype.multiplyVector3 = function ( vector ) { + + console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' ); + return vector.applyQuaternion( this ); + + }; + + Object.assign( Ray.prototype, { + + isIntersectionBox: function ( box ) { + + console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' ); + return this.intersectsBox( box ); + + }, + isIntersectionPlane: function ( plane ) { + + console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' ); + return this.intersectsPlane( plane ); + + }, + isIntersectionSphere: function ( sphere ) { + + console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' ); + return this.intersectsSphere( sphere ); + + } + + } ); + + Object.assign( Shape.prototype, { + + extractAllPoints: function ( divisions ) { + + console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' ); + return this.extractPoints( divisions ); + + }, + extrude: function ( options ) { + + console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' ); + return new ExtrudeGeometry( this, options ); + + }, + makeGeometry: function ( options ) { + + console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' ); + return new ShapeGeometry( this, options ); + + } + + } ); + + Object.assign( Vector2.prototype, { + + fromAttribute: function ( attribute, index, offset ) { + + console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' ); + return this.fromBufferAttribute( attribute, index, offset ); + + }, + distanceToManhattan: function ( v ) { + + console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' ); + return this.manhattanDistanceTo( v ); + + }, + lengthManhattan: function () { + + console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' ); + return this.manhattanLength(); + + } + + } ); + + Object.assign( Vector3.prototype, { + + setEulerFromRotationMatrix: function () { + + console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' ); + + }, + setEulerFromQuaternion: function () { + + console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' ); + + }, + getPositionFromMatrix: function ( m ) { + + console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' ); + return this.setFromMatrixPosition( m ); + + }, + getScaleFromMatrix: function ( m ) { + + console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' ); + return this.setFromMatrixScale( m ); + + }, + getColumnFromMatrix: function ( index, matrix ) { + + console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' ); + return this.setFromMatrixColumn( matrix, index ); + + }, + applyProjection: function ( m ) { + + console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' ); + return this.applyMatrix4( m ); + + }, + fromAttribute: function ( attribute, index, offset ) { + + console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' ); + return this.fromBufferAttribute( attribute, index, offset ); + + }, + distanceToManhattan: function ( v ) { + + console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' ); + return this.manhattanDistanceTo( v ); + + }, + lengthManhattan: function () { + + console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' ); + return this.manhattanLength(); + + } + + } ); + + Object.assign( Vector4.prototype, { + + fromAttribute: function ( attribute, index, offset ) { + + console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' ); + return this.fromBufferAttribute( attribute, index, offset ); + + }, + lengthManhattan: function () { + + console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' ); + return this.manhattanLength(); + + } + + } ); + + // + + Object.assign( Geometry.prototype, { + + computeTangents: function () { + + console.error( 'THREE.Geometry: .computeTangents() has been removed.' ); + + }, + computeLineDistances: function () { + + console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' ); + + } + + } ); + + Object.assign( Object3D.prototype, { + + getChildByName: function ( name ) { + + console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' ); + return this.getObjectByName( name ); + + }, + renderDepth: function () { + + console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' ); + + }, + translate: function ( distance, axis ) { + + console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' ); + return this.translateOnAxis( axis, distance ); + + } + + } ); + + Object.defineProperties( Object3D.prototype, { + + eulerOrder: { + get: function () { + + console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' ); + return this.rotation.order; + + }, + set: function ( value ) { + + console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' ); + this.rotation.order = value; + + } + }, + useQuaternion: { + get: function () { + + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); + + }, + set: function () { + + console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' ); + + } + } + + } ); + + Object.defineProperties( LOD.prototype, { + + objects: { + get: function () { + + console.warn( 'THREE.LOD: .objects has been renamed to .levels.' ); + return this.levels; + + } + } + + } ); + + Object.defineProperty( Skeleton.prototype, 'useVertexTexture', { + + get: function () { + + console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' ); + + }, + set: function () { + + console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' ); + + } + + } ); + + Object.defineProperty( Curve.prototype, '__arcLengthDivisions', { + + get: function () { + + console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' ); + return this.arcLengthDivisions; + + }, + set: function ( value ) { + + console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' ); + this.arcLengthDivisions = value; + + } + + } ); + + // + + PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) { + + console.warn( "THREE.PerspectiveCamera.setLens is deprecated. " + + "Use .setFocalLength and .filmGauge for a photographic setup." ); + + if ( filmGauge !== undefined ) this.filmGauge = filmGauge; + this.setFocalLength( focalLength ); + + }; + + // + + Object.defineProperties( Light.prototype, { + onlyShadow: { + set: function () { + + console.warn( 'THREE.Light: .onlyShadow has been removed.' ); + + } + }, + shadowCameraFov: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' ); + this.shadow.camera.fov = value; + + } + }, + shadowCameraLeft: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' ); + this.shadow.camera.left = value; + + } + }, + shadowCameraRight: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' ); + this.shadow.camera.right = value; + + } + }, + shadowCameraTop: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' ); + this.shadow.camera.top = value; + + } + }, + shadowCameraBottom: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' ); + this.shadow.camera.bottom = value; + + } + }, + shadowCameraNear: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' ); + this.shadow.camera.near = value; + + } + }, + shadowCameraFar: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' ); + this.shadow.camera.far = value; + + } + }, + shadowCameraVisible: { + set: function () { + + console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' ); + + } + }, + shadowBias: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' ); + this.shadow.bias = value; + + } + }, + shadowDarkness: { + set: function () { + + console.warn( 'THREE.Light: .shadowDarkness has been removed.' ); + + } + }, + shadowMapWidth: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' ); + this.shadow.mapSize.width = value; + + } + }, + shadowMapHeight: { + set: function ( value ) { + + console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' ); + this.shadow.mapSize.height = value; + + } + } + } ); + + // + + Object.defineProperties( BufferAttribute.prototype, { + + length: { + get: function () { + + console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' ); + return this.array.length; + + } + }, + copyIndicesArray: function ( /* indices */ ) { + + console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' ); + + } + + } ); + + Object.assign( BufferGeometry.prototype, { + + addIndex: function ( index ) { + + console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' ); + this.setIndex( index ); + + }, + addDrawCall: function ( start, count, indexOffset ) { + + if ( indexOffset !== undefined ) { + + console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' ); + + } + console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' ); + this.addGroup( start, count ); + + }, + clearDrawCalls: function () { + + console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' ); + this.clearGroups(); + + }, + computeTangents: function () { + + console.warn( 'THREE.BufferGeometry: .computeTangents() has been removed.' ); + + }, + computeOffsets: function () { + + console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' ); + + } + + } ); + + Object.defineProperties( BufferGeometry.prototype, { + + drawcalls: { + get: function () { + + console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' ); + return this.groups; + + } + }, + offsets: { + get: function () { + + console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' ); + return this.groups; + + } + } + + } ); + + // + + Object.defineProperties( Uniform.prototype, { + + dynamic: { + set: function () { + + console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' ); + + } + }, + onUpdate: { + value: function () { + + console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' ); + return this; + + } + } + + } ); + + // + + Object.defineProperties( Material.prototype, { + + wrapAround: { + get: function () { + + console.warn( 'THREE.Material: .wrapAround has been removed.' ); + + }, + set: function () { + + console.warn( 'THREE.Material: .wrapAround has been removed.' ); + + } + }, + wrapRGB: { + get: function () { + + console.warn( 'THREE.Material: .wrapRGB has been removed.' ); + return new Color(); + + } + }, + + shading: { + get: function () { + + console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); + + }, + set: function ( value ) { + + console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' ); + this.flatShading = ( value === FlatShading ); + + } + } + + } ); + + Object.defineProperties( MeshPhongMaterial.prototype, { + + metal: { + get: function () { + + console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' ); + return false; + + }, + set: function () { + + console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' ); + + } + } + + } ); + + Object.defineProperties( ShaderMaterial.prototype, { + + derivatives: { + get: function () { + + console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' ); + return this.extensions.derivatives; + + }, + set: function ( value ) { + + console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' ); + this.extensions.derivatives = value; + + } + } + + } ); + + // + + Object.assign( WebGLRenderer.prototype, { + + getCurrentRenderTarget: function () { + + console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' ); + return this.getRenderTarget(); + + }, + + getMaxAnisotropy: function () { + + console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' ); + return this.capabilities.getMaxAnisotropy(); + + }, + + getPrecision: function () { + + console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' ); + return this.capabilities.precision; + + }, + + resetGLState: function () { + + console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' ); + return this.state.reset(); + + }, + + supportsFloatTextures: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' ); + return this.extensions.get( 'OES_texture_float' ); + + }, + supportsHalfFloatTextures: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' ); + return this.extensions.get( 'OES_texture_half_float' ); + + }, + supportsStandardDerivatives: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' ); + return this.extensions.get( 'OES_standard_derivatives' ); + + }, + supportsCompressedTextureS3TC: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' ); + return this.extensions.get( 'WEBGL_compressed_texture_s3tc' ); + + }, + supportsCompressedTexturePVRTC: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' ); + return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' ); + + }, + supportsBlendMinMax: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' ); + return this.extensions.get( 'EXT_blend_minmax' ); + + }, + supportsVertexTextures: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' ); + return this.capabilities.vertexTextures; + + }, + supportsInstancedArrays: function () { + + console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' ); + return this.extensions.get( 'ANGLE_instanced_arrays' ); + + }, + enableScissorTest: function ( boolean ) { + + console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' ); + this.setScissorTest( boolean ); + + }, + initMaterial: function () { + + console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' ); + + }, + addPrePlugin: function () { + + console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' ); + + }, + addPostPlugin: function () { + + console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' ); + + }, + updateShadowMap: function () { + + console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' ); + + }, + setFaceCulling: function () { + + console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' ); + + } + + } ); + + Object.defineProperties( WebGLRenderer.prototype, { + + shadowMapEnabled: { + get: function () { + + return this.shadowMap.enabled; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' ); + this.shadowMap.enabled = value; + + } + }, + shadowMapType: { + get: function () { + + return this.shadowMap.type; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' ); + this.shadowMap.type = value; + + } + }, + shadowMapCullFace: { + get: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' ); + return undefined; + + }, + set: function ( /* value */ ) { + + console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' ); + + } + } + } ); + + Object.defineProperties( WebGLShadowMap.prototype, { + + cullFace: { + get: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' ); + return undefined; + + }, + set: function ( /* cullFace */ ) { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' ); + + } + }, + renderReverseSided: { + get: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' ); + return undefined; + + }, + set: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' ); + + } + }, + renderSingleSided: { + get: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' ); + return undefined; + + }, + set: function () { + + console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' ); + + } + } + + } ); + + // + + Object.defineProperties( WebGLRenderTarget.prototype, { + + wrapS: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' ); + return this.texture.wrapS; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' ); + this.texture.wrapS = value; + + } + }, + wrapT: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' ); + return this.texture.wrapT; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' ); + this.texture.wrapT = value; + + } + }, + magFilter: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' ); + return this.texture.magFilter; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' ); + this.texture.magFilter = value; + + } + }, + minFilter: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' ); + return this.texture.minFilter; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' ); + this.texture.minFilter = value; + + } + }, + anisotropy: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' ); + return this.texture.anisotropy; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' ); + this.texture.anisotropy = value; + + } + }, + offset: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' ); + return this.texture.offset; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' ); + this.texture.offset = value; + + } + }, + repeat: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' ); + return this.texture.repeat; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' ); + this.texture.repeat = value; + + } + }, + format: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' ); + return this.texture.format; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' ); + this.texture.format = value; + + } + }, + type: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' ); + return this.texture.type; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' ); + this.texture.type = value; + + } + }, + generateMipmaps: { + get: function () { + + console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' ); + return this.texture.generateMipmaps; + + }, + set: function ( value ) { + + console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' ); + this.texture.generateMipmaps = value; + + } + } + + } ); + + // + + Object.defineProperties( WebVRManager.prototype, { + + standing: { + set: function ( /* value */ ) { + + console.warn( 'THREE.WebVRManager: .standing has been removed.' ); + + } + } + + } ); + + // + + Audio.prototype.load = function ( file ) { + + console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' ); + var scope = this; + var audioLoader = new AudioLoader(); + audioLoader.load( file, function ( buffer ) { + + scope.setBuffer( buffer ); + + } ); + return this; + + }; + + AudioAnalyser.prototype.getData = function () { + + console.warn( 'THREE.AudioAnalyser: .getData() is now .getFrequencyData().' ); + return this.getFrequencyData(); + + }; + + // + + CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) { + + console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' ); + return this.update( renderer, scene ); + + }; + + // + + var GeometryUtils = { + + merge: function ( geometry1, geometry2, materialIndexOffset ) { + + console.warn( 'THREE.GeometryUtils: .merge() has been moved to Geometry. Use geometry.merge( geometry2, matrix, materialIndexOffset ) instead.' ); + var matrix; + + if ( geometry2.isMesh ) { + + geometry2.matrixAutoUpdate && geometry2.updateMatrix(); + + matrix = geometry2.matrix; + geometry2 = geometry2.geometry; + + } + + geometry1.merge( geometry2, matrix, materialIndexOffset ); + + }, + + center: function ( geometry ) { + + console.warn( 'THREE.GeometryUtils: .center() has been moved to Geometry. Use geometry.center() instead.' ); + return geometry.center(); + + } + + }; + + var ImageUtils = { + + crossOrigin: undefined, + + loadTexture: function ( url, mapping, onLoad, onError ) { + + console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' ); + + var loader = new TextureLoader(); + loader.setCrossOrigin( this.crossOrigin ); + + var texture = loader.load( url, onLoad, undefined, onError ); + + if ( mapping ) texture.mapping = mapping; + + return texture; + + }, + + loadTextureCube: function ( urls, mapping, onLoad, onError ) { + + console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' ); + + var loader = new CubeTextureLoader(); + loader.setCrossOrigin( this.crossOrigin ); + + var texture = loader.load( urls, onLoad, undefined, onError ); + + if ( mapping ) texture.mapping = mapping; + + return texture; + + }, + + loadCompressedTexture: function () { + + console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' ); + + }, + + loadCompressedTextureCube: function () { + + console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' ); + + } + + }; + + // + + function Projector() { + + console.error( 'THREE.Projector has been moved to /examples/js/renderers/Projector.js.' ); + + this.projectVector = function ( vector, camera ) { + + console.warn( 'THREE.Projector: .projectVector() is now vector.project().' ); + vector.project( camera ); + + }; + + this.unprojectVector = function ( vector, camera ) { + + console.warn( 'THREE.Projector: .unprojectVector() is now vector.unproject().' ); + vector.unproject( camera ); + + }; + + this.pickingRay = function () { + + console.error( 'THREE.Projector: .pickingRay() is now raycaster.setFromCamera().' ); + + }; + + } + + // + + function CanvasRenderer() { + + console.error( 'THREE.CanvasRenderer has been moved to /examples/js/renderers/CanvasRenderer.js' ); + + this.domElement = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' ); + this.clear = function () {}; + this.render = function () {}; + this.setClearColor = function () {}; + this.setSize = function () {}; + + } + + // + + var SceneUtils = { + + createMultiMaterialObject: function ( /* geometry, materials */ ) { + + console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' ); + + }, + + detach: function ( /* child, parent, scene */ ) { + + console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' ); + + }, + + attach: function ( /* child, scene, parent */ ) { + + console.error( 'THREE.SceneUtils has been moved to /examples/js/utils/SceneUtils.js' ); + + } + + }; + + // + + function LensFlare() { + + console.error( 'THREE.LensFlare has been moved to /examples/js/objects/Lensflare.js' ); + + } + + exports.WebGLRenderTargetCube = WebGLRenderTargetCube; + exports.WebGLRenderTarget = WebGLRenderTarget; + exports.WebGLRenderer = WebGLRenderer; + exports.ShaderLib = ShaderLib; + exports.UniformsLib = UniformsLib; + exports.UniformsUtils = UniformsUtils; + exports.ShaderChunk = ShaderChunk; + exports.FogExp2 = FogExp2; + exports.Fog = Fog; + exports.Scene = Scene; + exports.Sprite = Sprite; + exports.LOD = LOD; + exports.SkinnedMesh = SkinnedMesh; + exports.Skeleton = Skeleton; + exports.Bone = Bone; + exports.Mesh = Mesh; + exports.LineSegments = LineSegments; + exports.LineLoop = LineLoop; + exports.Line = Line; + exports.Points = Points; + exports.Group = Group; + exports.VideoTexture = VideoTexture; + exports.DataTexture = DataTexture; + exports.CompressedTexture = CompressedTexture; + exports.CubeTexture = CubeTexture; + exports.CanvasTexture = CanvasTexture; + exports.DepthTexture = DepthTexture; + exports.Texture = Texture; + exports.CompressedTextureLoader = CompressedTextureLoader; + exports.DataTextureLoader = DataTextureLoader; + exports.CubeTextureLoader = CubeTextureLoader; + exports.TextureLoader = TextureLoader; + exports.ObjectLoader = ObjectLoader; + exports.MaterialLoader = MaterialLoader; + exports.BufferGeometryLoader = BufferGeometryLoader; + exports.DefaultLoadingManager = DefaultLoadingManager; + exports.LoadingManager = LoadingManager; + exports.JSONLoader = JSONLoader; + exports.ImageLoader = ImageLoader; + exports.ImageBitmapLoader = ImageBitmapLoader; + exports.FontLoader = FontLoader; + exports.FileLoader = FileLoader; + exports.Loader = Loader; + exports.LoaderUtils = LoaderUtils; + exports.Cache = Cache; + exports.AudioLoader = AudioLoader; + exports.SpotLightShadow = SpotLightShadow; + exports.SpotLight = SpotLight; + exports.PointLight = PointLight; + exports.RectAreaLight = RectAreaLight; + exports.HemisphereLight = HemisphereLight; + exports.DirectionalLightShadow = DirectionalLightShadow; + exports.DirectionalLight = DirectionalLight; + exports.AmbientLight = AmbientLight; + exports.LightShadow = LightShadow; + exports.Light = Light; + exports.StereoCamera = StereoCamera; + exports.PerspectiveCamera = PerspectiveCamera; + exports.OrthographicCamera = OrthographicCamera; + exports.CubeCamera = CubeCamera; + exports.ArrayCamera = ArrayCamera; + exports.Camera = Camera; + exports.AudioListener = AudioListener; + exports.PositionalAudio = PositionalAudio; + exports.AudioContext = AudioContext; + exports.AudioAnalyser = AudioAnalyser; + exports.Audio = Audio; + exports.VectorKeyframeTrack = VectorKeyframeTrack; + exports.StringKeyframeTrack = StringKeyframeTrack; + exports.QuaternionKeyframeTrack = QuaternionKeyframeTrack; + exports.NumberKeyframeTrack = NumberKeyframeTrack; + exports.ColorKeyframeTrack = ColorKeyframeTrack; + exports.BooleanKeyframeTrack = BooleanKeyframeTrack; + exports.PropertyMixer = PropertyMixer; + exports.PropertyBinding = PropertyBinding; + exports.KeyframeTrack = KeyframeTrack; + exports.AnimationUtils = AnimationUtils; + exports.AnimationObjectGroup = AnimationObjectGroup; + exports.AnimationMixer = AnimationMixer; + exports.AnimationClip = AnimationClip; + exports.Uniform = Uniform; + exports.InstancedBufferGeometry = InstancedBufferGeometry; + exports.BufferGeometry = BufferGeometry; + exports.Geometry = Geometry; + exports.InterleavedBufferAttribute = InterleavedBufferAttribute; + exports.InstancedInterleavedBuffer = InstancedInterleavedBuffer; + exports.InterleavedBuffer = InterleavedBuffer; + exports.InstancedBufferAttribute = InstancedBufferAttribute; + exports.Face3 = Face3; + exports.Object3D = Object3D; + exports.Raycaster = Raycaster; + exports.Layers = Layers; + exports.EventDispatcher = EventDispatcher; + exports.Clock = Clock; + exports.QuaternionLinearInterpolant = QuaternionLinearInterpolant; + exports.LinearInterpolant = LinearInterpolant; + exports.DiscreteInterpolant = DiscreteInterpolant; + exports.CubicInterpolant = CubicInterpolant; + exports.Interpolant = Interpolant; + exports.Triangle = Triangle; + exports.Math = _Math; + exports.Spherical = Spherical; + exports.Cylindrical = Cylindrical; + exports.Plane = Plane; + exports.Frustum = Frustum; + exports.Sphere = Sphere; + exports.Ray = Ray; + exports.Matrix4 = Matrix4; + exports.Matrix3 = Matrix3; + exports.Box3 = Box3; + exports.Box2 = Box2; + exports.Line3 = Line3; + exports.Euler = Euler; + exports.Vector4 = Vector4; + exports.Vector3 = Vector3; + exports.Vector2 = Vector2; + exports.Quaternion = Quaternion; + exports.Color = Color; + exports.ImmediateRenderObject = ImmediateRenderObject; + exports.VertexNormalsHelper = VertexNormalsHelper; + exports.SpotLightHelper = SpotLightHelper; + exports.SkeletonHelper = SkeletonHelper; + exports.PointLightHelper = PointLightHelper; + exports.RectAreaLightHelper = RectAreaLightHelper; + exports.HemisphereLightHelper = HemisphereLightHelper; + exports.GridHelper = GridHelper; + exports.PolarGridHelper = PolarGridHelper; + exports.FaceNormalsHelper = FaceNormalsHelper; + exports.DirectionalLightHelper = DirectionalLightHelper; + exports.CameraHelper = CameraHelper; + exports.BoxHelper = BoxHelper; + exports.Box3Helper = Box3Helper; + exports.PlaneHelper = PlaneHelper; + exports.ArrowHelper = ArrowHelper; + exports.AxesHelper = AxesHelper; + exports.Shape = Shape; + exports.Path = Path; + exports.ShapePath = ShapePath; + exports.Font = Font; + exports.CurvePath = CurvePath; + exports.Curve = Curve; + exports.ShapeUtils = ShapeUtils; + exports.WebGLUtils = WebGLUtils; + exports.WireframeGeometry = WireframeGeometry; + exports.ParametricGeometry = ParametricGeometry; + exports.ParametricBufferGeometry = ParametricBufferGeometry; + exports.TetrahedronGeometry = TetrahedronGeometry; + exports.TetrahedronBufferGeometry = TetrahedronBufferGeometry; + exports.OctahedronGeometry = OctahedronGeometry; + exports.OctahedronBufferGeometry = OctahedronBufferGeometry; + exports.IcosahedronGeometry = IcosahedronGeometry; + exports.IcosahedronBufferGeometry = IcosahedronBufferGeometry; + exports.DodecahedronGeometry = DodecahedronGeometry; + exports.DodecahedronBufferGeometry = DodecahedronBufferGeometry; + exports.PolyhedronGeometry = PolyhedronGeometry; + exports.PolyhedronBufferGeometry = PolyhedronBufferGeometry; + exports.TubeGeometry = TubeGeometry; + exports.TubeBufferGeometry = TubeBufferGeometry; + exports.TorusKnotGeometry = TorusKnotGeometry; + exports.TorusKnotBufferGeometry = TorusKnotBufferGeometry; + exports.TorusGeometry = TorusGeometry; + exports.TorusBufferGeometry = TorusBufferGeometry; + exports.TextGeometry = TextGeometry; + exports.TextBufferGeometry = TextBufferGeometry; + exports.SphereGeometry = SphereGeometry; + exports.SphereBufferGeometry = SphereBufferGeometry; + exports.RingGeometry = RingGeometry; + exports.RingBufferGeometry = RingBufferGeometry; + exports.PlaneGeometry = PlaneGeometry; + exports.PlaneBufferGeometry = PlaneBufferGeometry; + exports.LatheGeometry = LatheGeometry; + exports.LatheBufferGeometry = LatheBufferGeometry; + exports.ShapeGeometry = ShapeGeometry; + exports.ShapeBufferGeometry = ShapeBufferGeometry; + exports.ExtrudeGeometry = ExtrudeGeometry; + exports.ExtrudeBufferGeometry = ExtrudeBufferGeometry; + exports.EdgesGeometry = EdgesGeometry; + exports.ConeGeometry = ConeGeometry; + exports.ConeBufferGeometry = ConeBufferGeometry; + exports.CylinderGeometry = CylinderGeometry; + exports.CylinderBufferGeometry = CylinderBufferGeometry; + exports.CircleGeometry = CircleGeometry; + exports.CircleBufferGeometry = CircleBufferGeometry; + exports.BoxGeometry = BoxGeometry; + exports.BoxBufferGeometry = BoxBufferGeometry; + exports.ShadowMaterial = ShadowMaterial; + exports.SpriteMaterial = SpriteMaterial; + exports.RawShaderMaterial = RawShaderMaterial; + exports.ShaderMaterial = ShaderMaterial; + exports.PointsMaterial = PointsMaterial; + exports.MeshPhysicalMaterial = MeshPhysicalMaterial; + exports.MeshStandardMaterial = MeshStandardMaterial; + exports.MeshPhongMaterial = MeshPhongMaterial; + exports.MeshToonMaterial = MeshToonMaterial; + exports.MeshNormalMaterial = MeshNormalMaterial; + exports.MeshLambertMaterial = MeshLambertMaterial; + exports.MeshDepthMaterial = MeshDepthMaterial; + exports.MeshDistanceMaterial = MeshDistanceMaterial; + exports.MeshBasicMaterial = MeshBasicMaterial; + exports.LineDashedMaterial = LineDashedMaterial; + exports.LineBasicMaterial = LineBasicMaterial; + exports.Material = Material; + exports.Float64BufferAttribute = Float64BufferAttribute; + exports.Float32BufferAttribute = Float32BufferAttribute; + exports.Uint32BufferAttribute = Uint32BufferAttribute; + exports.Int32BufferAttribute = Int32BufferAttribute; + exports.Uint16BufferAttribute = Uint16BufferAttribute; + exports.Int16BufferAttribute = Int16BufferAttribute; + exports.Uint8ClampedBufferAttribute = Uint8ClampedBufferAttribute; + exports.Uint8BufferAttribute = Uint8BufferAttribute; + exports.Int8BufferAttribute = Int8BufferAttribute; + exports.BufferAttribute = BufferAttribute; + exports.ArcCurve = ArcCurve; + exports.CatmullRomCurve3 = CatmullRomCurve3; + exports.CubicBezierCurve = CubicBezierCurve; + exports.CubicBezierCurve3 = CubicBezierCurve3; + exports.EllipseCurve = EllipseCurve; + exports.LineCurve = LineCurve; + exports.LineCurve3 = LineCurve3; + exports.QuadraticBezierCurve = QuadraticBezierCurve; + exports.QuadraticBezierCurve3 = QuadraticBezierCurve3; + exports.SplineCurve = SplineCurve; + exports.REVISION = REVISION; + exports.MOUSE = MOUSE; + exports.CullFaceNone = CullFaceNone; + exports.CullFaceBack = CullFaceBack; + exports.CullFaceFront = CullFaceFront; + exports.CullFaceFrontBack = CullFaceFrontBack; + exports.FrontFaceDirectionCW = FrontFaceDirectionCW; + exports.FrontFaceDirectionCCW = FrontFaceDirectionCCW; + exports.BasicShadowMap = BasicShadowMap; + exports.PCFShadowMap = PCFShadowMap; + exports.PCFSoftShadowMap = PCFSoftShadowMap; + exports.FrontSide = FrontSide; + exports.BackSide = BackSide; + exports.DoubleSide = DoubleSide; + exports.FlatShading = FlatShading; + exports.SmoothShading = SmoothShading; + exports.NoColors = NoColors; + exports.FaceColors = FaceColors; + exports.VertexColors = VertexColors; + exports.NoBlending = NoBlending; + exports.NormalBlending = NormalBlending; + exports.AdditiveBlending = AdditiveBlending; + exports.SubtractiveBlending = SubtractiveBlending; + exports.MultiplyBlending = MultiplyBlending; + exports.CustomBlending = CustomBlending; + exports.AddEquation = AddEquation; + exports.SubtractEquation = SubtractEquation; + exports.ReverseSubtractEquation = ReverseSubtractEquation; + exports.MinEquation = MinEquation; + exports.MaxEquation = MaxEquation; + exports.ZeroFactor = ZeroFactor; + exports.OneFactor = OneFactor; + exports.SrcColorFactor = SrcColorFactor; + exports.OneMinusSrcColorFactor = OneMinusSrcColorFactor; + exports.SrcAlphaFactor = SrcAlphaFactor; + exports.OneMinusSrcAlphaFactor = OneMinusSrcAlphaFactor; + exports.DstAlphaFactor = DstAlphaFactor; + exports.OneMinusDstAlphaFactor = OneMinusDstAlphaFactor; + exports.DstColorFactor = DstColorFactor; + exports.OneMinusDstColorFactor = OneMinusDstColorFactor; + exports.SrcAlphaSaturateFactor = SrcAlphaSaturateFactor; + exports.NeverDepth = NeverDepth; + exports.AlwaysDepth = AlwaysDepth; + exports.LessDepth = LessDepth; + exports.LessEqualDepth = LessEqualDepth; + exports.EqualDepth = EqualDepth; + exports.GreaterEqualDepth = GreaterEqualDepth; + exports.GreaterDepth = GreaterDepth; + exports.NotEqualDepth = NotEqualDepth; + exports.MultiplyOperation = MultiplyOperation; + exports.MixOperation = MixOperation; + exports.AddOperation = AddOperation; + exports.NoToneMapping = NoToneMapping; + exports.LinearToneMapping = LinearToneMapping; + exports.ReinhardToneMapping = ReinhardToneMapping; + exports.Uncharted2ToneMapping = Uncharted2ToneMapping; + exports.CineonToneMapping = CineonToneMapping; + exports.UVMapping = UVMapping; + exports.CubeReflectionMapping = CubeReflectionMapping; + exports.CubeRefractionMapping = CubeRefractionMapping; + exports.EquirectangularReflectionMapping = EquirectangularReflectionMapping; + exports.EquirectangularRefractionMapping = EquirectangularRefractionMapping; + exports.SphericalReflectionMapping = SphericalReflectionMapping; + exports.CubeUVReflectionMapping = CubeUVReflectionMapping; + exports.CubeUVRefractionMapping = CubeUVRefractionMapping; + exports.RepeatWrapping = RepeatWrapping; + exports.ClampToEdgeWrapping = ClampToEdgeWrapping; + exports.MirroredRepeatWrapping = MirroredRepeatWrapping; + exports.NearestFilter = NearestFilter; + exports.NearestMipMapNearestFilter = NearestMipMapNearestFilter; + exports.NearestMipMapLinearFilter = NearestMipMapLinearFilter; + exports.LinearFilter = LinearFilter; + exports.LinearMipMapNearestFilter = LinearMipMapNearestFilter; + exports.LinearMipMapLinearFilter = LinearMipMapLinearFilter; + exports.UnsignedByteType = UnsignedByteType; + exports.ByteType = ByteType; + exports.ShortType = ShortType; + exports.UnsignedShortType = UnsignedShortType; + exports.IntType = IntType; + exports.UnsignedIntType = UnsignedIntType; + exports.FloatType = FloatType; + exports.HalfFloatType = HalfFloatType; + exports.UnsignedShort4444Type = UnsignedShort4444Type; + exports.UnsignedShort5551Type = UnsignedShort5551Type; + exports.UnsignedShort565Type = UnsignedShort565Type; + exports.UnsignedInt248Type = UnsignedInt248Type; + exports.AlphaFormat = AlphaFormat; + exports.RGBFormat = RGBFormat; + exports.RGBAFormat = RGBAFormat; + exports.LuminanceFormat = LuminanceFormat; + exports.LuminanceAlphaFormat = LuminanceAlphaFormat; + exports.RGBEFormat = RGBEFormat; + exports.DepthFormat = DepthFormat; + exports.DepthStencilFormat = DepthStencilFormat; + exports.RGB_S3TC_DXT1_Format = RGB_S3TC_DXT1_Format; + exports.RGBA_S3TC_DXT1_Format = RGBA_S3TC_DXT1_Format; + exports.RGBA_S3TC_DXT3_Format = RGBA_S3TC_DXT3_Format; + exports.RGBA_S3TC_DXT5_Format = RGBA_S3TC_DXT5_Format; + exports.RGB_PVRTC_4BPPV1_Format = RGB_PVRTC_4BPPV1_Format; + exports.RGB_PVRTC_2BPPV1_Format = RGB_PVRTC_2BPPV1_Format; + exports.RGBA_PVRTC_4BPPV1_Format = RGBA_PVRTC_4BPPV1_Format; + exports.RGBA_PVRTC_2BPPV1_Format = RGBA_PVRTC_2BPPV1_Format; + exports.RGB_ETC1_Format = RGB_ETC1_Format; + exports.RGBA_ASTC_4x4_Format = RGBA_ASTC_4x4_Format; + exports.RGBA_ASTC_5x4_Format = RGBA_ASTC_5x4_Format; + exports.RGBA_ASTC_5x5_Format = RGBA_ASTC_5x5_Format; + exports.RGBA_ASTC_6x5_Format = RGBA_ASTC_6x5_Format; + exports.RGBA_ASTC_6x6_Format = RGBA_ASTC_6x6_Format; + exports.RGBA_ASTC_8x5_Format = RGBA_ASTC_8x5_Format; + exports.RGBA_ASTC_8x6_Format = RGBA_ASTC_8x6_Format; + exports.RGBA_ASTC_8x8_Format = RGBA_ASTC_8x8_Format; + exports.RGBA_ASTC_10x5_Format = RGBA_ASTC_10x5_Format; + exports.RGBA_ASTC_10x6_Format = RGBA_ASTC_10x6_Format; + exports.RGBA_ASTC_10x8_Format = RGBA_ASTC_10x8_Format; + exports.RGBA_ASTC_10x10_Format = RGBA_ASTC_10x10_Format; + exports.RGBA_ASTC_12x10_Format = RGBA_ASTC_12x10_Format; + exports.RGBA_ASTC_12x12_Format = RGBA_ASTC_12x12_Format; + exports.LoopOnce = LoopOnce; + exports.LoopRepeat = LoopRepeat; + exports.LoopPingPong = LoopPingPong; + exports.InterpolateDiscrete = InterpolateDiscrete; + exports.InterpolateLinear = InterpolateLinear; + exports.InterpolateSmooth = InterpolateSmooth; + exports.ZeroCurvatureEnding = ZeroCurvatureEnding; + exports.ZeroSlopeEnding = ZeroSlopeEnding; + exports.WrapAroundEnding = WrapAroundEnding; + exports.TrianglesDrawMode = TrianglesDrawMode; + exports.TriangleStripDrawMode = TriangleStripDrawMode; + exports.TriangleFanDrawMode = TriangleFanDrawMode; + exports.LinearEncoding = LinearEncoding; + exports.sRGBEncoding = sRGBEncoding; + exports.GammaEncoding = GammaEncoding; + exports.RGBEEncoding = RGBEEncoding; + exports.LogLuvEncoding = LogLuvEncoding; + exports.RGBM7Encoding = RGBM7Encoding; + exports.RGBM16Encoding = RGBM16Encoding; + exports.RGBDEncoding = RGBDEncoding; + exports.BasicDepthPacking = BasicDepthPacking; + exports.RGBADepthPacking = RGBADepthPacking; + exports.CubeGeometry = BoxGeometry; + exports.Face4 = Face4; + exports.LineStrip = LineStrip; + exports.LinePieces = LinePieces; + exports.MeshFaceMaterial = MeshFaceMaterial; + exports.MultiMaterial = MultiMaterial; + exports.PointCloud = PointCloud; + exports.Particle = Particle; + exports.ParticleSystem = ParticleSystem; + exports.PointCloudMaterial = PointCloudMaterial; + exports.ParticleBasicMaterial = ParticleBasicMaterial; + exports.ParticleSystemMaterial = ParticleSystemMaterial; + exports.Vertex = Vertex; + exports.DynamicBufferAttribute = DynamicBufferAttribute; + exports.Int8Attribute = Int8Attribute; + exports.Uint8Attribute = Uint8Attribute; + exports.Uint8ClampedAttribute = Uint8ClampedAttribute; + exports.Int16Attribute = Int16Attribute; + exports.Uint16Attribute = Uint16Attribute; + exports.Int32Attribute = Int32Attribute; + exports.Uint32Attribute = Uint32Attribute; + exports.Float32Attribute = Float32Attribute; + exports.Float64Attribute = Float64Attribute; + exports.ClosedSplineCurve3 = ClosedSplineCurve3; + exports.SplineCurve3 = SplineCurve3; + exports.Spline = Spline; + exports.AxisHelper = AxisHelper; + exports.BoundingBoxHelper = BoundingBoxHelper; + exports.EdgesHelper = EdgesHelper; + exports.WireframeHelper = WireframeHelper; + exports.XHRLoader = XHRLoader; + exports.BinaryTextureLoader = BinaryTextureLoader; + exports.GeometryUtils = GeometryUtils; + exports.ImageUtils = ImageUtils; + exports.Projector = Projector; + exports.CanvasRenderer = CanvasRenderer; + exports.SceneUtils = SceneUtils; + exports.LensFlare = LensFlare; + + Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/static/exercises/visual_odometry_3D/js/autocorrector.js b/static/exercises/visual_odometry_3D/js/autocorrector.js new file mode 100644 index 000000000..9fbc233a8 --- /dev/null +++ b/static/exercises/visual_odometry_3D/js/autocorrector.js @@ -0,0 +1,20 @@ +var scoreElement = document.getElementById("score-number"); + +function updateScore(number) { + var actualScore = parseInt(scoreElement.innerText.split(":")[1]); + var newScore = actualScore + number; + scoreElement.innerText = "Score: " + newScore; +} + +function autocorrector(truePosition, estimatedPosition) { + // Euclide distance + var x = truePosition.x - estimatedPosition.x + var y = truePosition.y - estimatedPosition.y + var z = truePosition.z - estimatedPosition.z + + var d = Math.sqrt(x * x + y * y + z * z); + + var increment = Math.round(5 / (1 + Math.exp(-(Math.abs(d) - 30)))); + + updateScore(increment); + } diff --git a/static/exercises/visual_odometry_3D/js/controller.js b/static/exercises/visual_odometry_3D/js/controller.js new file mode 100644 index 000000000..7fa4a8786 --- /dev/null +++ b/static/exercises/visual_odometry_3D/js/controller.js @@ -0,0 +1,40 @@ +// Main message controller for websockets +var running = true; +// Var to indicate whether a reset was requested +var resetRequested = false; +var firstCodeSent = false; + +// Function to resume the simulation +function start(){ + // Manager Websocket + editorChanged(false); + checkCode(); + togglePlayPause(true); +} + +// Function to request to load the student code into the robot +function check() { + editorChanged(false); + toggleSubmitButton(false); + checkCode(); + +} + +// Function to stop the student solution +function stop(){ + //stopCode(); // should be replaced by pauseBrain() when available + stopCode(); + + togglePlayPause(false); +} + +// Function to reset the simulation +function resetSim(){ + resetRequested = true; + + // Manager Websocket + resetBrain(); + resetSimulation(); + + running = false; +} \ No newline at end of file diff --git a/static/exercises/visual_odometry_3D/js/ws_code.js b/static/exercises/visual_odometry_3D/js/ws_code.js new file mode 100644 index 000000000..3d274b6c6 --- /dev/null +++ b/static/exercises/visual_odometry_3D/js/ws_code.js @@ -0,0 +1,110 @@ +//Editor Part +var editor = ace.edit("editor"); +editor.setTheme("ace/theme/monokai"); +editor.session.setMode("ace/mode/python"); + +/*var stop_button = document.getElementById("stop"); +stop_button.disabled = true; +stop_button.style.opacity = "0.4"; +stop_button.style.cursor = "not-allowed"; +*/ + +// running variable for psuedo decoupling +// Play/Pause from Reset +var frequency = "0", + running = false; + +var firstCodeSent = false; + +//Code for Websocket +var websocket_code; +function declare_code(websocket_address){ + websocket_code = new WebSocket(websocket_address); + + websocket_code.onopen = function(event){ + connectionUpdate({connection: 'exercise', command: 'launch_level', level: '5'}, '*'); + if (websocket_gui.readyState == 1) { + alert("[open] Connection established!"); + connectionUpdate({connection: 'exercise', command: 'up'}, '*'); + } + websocket_code.send("#ping"); + } + websocket_code.onclose = function(event){ + if(event.wasClean){ + alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + } + else{ + alert("[close] Connection closed!"); + } + } + + websocket_code.onmessage = function(event){ + var source_code = event.data; + operation = source_code.substring(0, 5); + + if(operation == "#load"){ + editor.setValue(source_code.substring(5,)); + } + else if(operation == "#freq"){ + var frequency_message = JSON.parse(source_code.substring(5,)); + // Parse GUI and Brain frequencies + document.querySelector("#ideal_gui_frequency").value = frequency_message.gui; + document.querySelector('#ideal_code_frequency').value = frequency_message.brain; + // Send the acknowledgment message along with frequency + code_frequency = document.querySelector('#code_freq').value; + gui_frequency = document.querySelector('#gui_freq').value; + frequency_message = {"brain": code_frequency, "gui": gui_frequency}; + websocket_code.send("#freq" + JSON.stringify(frequency_message)); + } + else if (operation == "#ping"){ + websocket_code.send("#ping"); + } + else if (operation == "#exec") { + toggleSubmitButton(true); + } + }; +} + +// Function that sends/submits the code! +function submitCode(){ + // Get the code from editor and add headers + var python_code = editor.getValue(); + python_code = "#code\n" + python_code + + websocket_code.send(python_code); + console.log("Code Sent! Check terminal for more information!"); + + running = true; + firstCodeSent = true; + //alert("Connection must be established before sending the code.") +} + +// Function that send/submits an empty string +function stopCode(){ + var stop_code = "#code\n"; + console.log("Message sent!"); + websocket_code.send(stop_code); + + running = false; +} + +function resetSim(){ + // Send message to initiate reset + var message = "#rest" + websocket_code.send(message) + + if(running == true){ + stopCode(); + submitCode(); + } +} + +// Function for range slider +function codefrequencyUpdate(vol) { + document.querySelector('#code_freq').value = vol; +} + +// Function for range slider +function guifrequencyUpdate(vol) { + document.querySelector('#gui_freq').value = vol; +} \ No newline at end of file diff --git a/static/exercises/visual_odometry_3D/js/ws_gui.js b/static/exercises/visual_odometry_3D/js/ws_gui.js new file mode 100644 index 000000000..4102e7b6e --- /dev/null +++ b/static/exercises/visual_odometry_3D/js/ws_gui.js @@ -0,0 +1,125 @@ +// To decode the image string we will receive from server +function decode_utf8(s) { + return decodeURIComponent(escape(s)) +} + +// Websocket and other variables for image display +var websocket_gui, animation_id; +var canvas = document.getElementById("gui_canvas"), + context = canvas.getContext('2d'); +var image = new Image(); +image.src = "/static/exercises/assets/kitti/dataset/sequences/01/image_0/000000.png"; + +function declare_gui(websocket_address) { + websocket_gui = new WebSocket(websocket_address); + + websocket_gui.onopen = function (event) { + //alert("[open] Connection established!"); + set_launch_level(get_launch_level() + 1); + if (websocket_code.readyState == 1) { + alert("[open] Connection established!"); + connectionUpdate({ connection: 'exercise', command: 'up' }, '*'); + } + } + + + websocket_gui.onclose = function (event) { + connectionUpdate({ connection: 'exercise', command: 'down' }, '*'); + if (event.wasClean) { + //alert(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); + } else { + //alert("[close] Connection closed!"); + } + } + + // What to do when a message from server is received + websocket_gui.onmessage = function (event) { + operation = event.data.substring(0, 4); + + if (operation == "#gui") { + // Parse the entire Object + var data = JSON.parse(event.data.substring(4)); + + // Parse the Image Data + var image_data = JSON.parse(data.image), + source = decode_utf8(image_data.image), + shape = image_data.shape, + counter = image_data.counter, + true_euler_angles = image_data.true_euler_angles, + true_position = image_data.true_position, + estimated_euler_angles = image_data.estimated_euler_angles, + estimated_position = image_data.estimated_position; + + if (true_euler_angles) + { + true_euler_angles = JSON.parse(image_data.true_euler_angles), + trueFrame.rotation.x = true_euler_angles.yaw + trueFrame.rotation.y = true_euler_angles.pitch + trueFrame.rotation.z = true_euler_angles.roll + } + if (true_position) + { + true_position = JSON.parse(image_data.true_position), + trueFrame.position.x = true_position.x * 1.0 + trueFrame.position.y = true_position.y * 1.0 + trueFrame.position.z = true_position.z * 1.0 + true_tracker.push(trueFrame.position.clone()) + } + if (estimated_euler_angles) + { + estimated_euler_angles = JSON.parse(image_data.estimated_euler_angles), + userFrame.rotation.x = estimated_euler_angles.yaw + userFrame.rotation.y = estimated_euler_angles.pitch + userFrame.rotation.z = estimated_euler_angles.roll + } + if (estimated_position) + { + estimated_position = JSON.parse(image_data.estimated_position), + userFrame.position.x = estimated_position.x * 1.0 + userFrame.position.y = estimated_position.y * 1.0 + userFrame.position.z = estimated_position.z * 1.0 + user_tracker.push(userFrame.position.clone()) + } + + if (estimated_position && true_position && (counter % 24) === 0) + { + autocorrector(true_position, estimated_position); + } + + if (!(track === [])) { + scene.remove(track) + track = createTrack(user_tracker, USER_COLOR) + trackGT = createTrack(true_tracker, TRUE_COLOR) + scene.add(track) + scene.add(trackGT) + } + + // Update Orbital Controls + controls.update() + + // Render + renderer.render(scene, camera) + + if (source != "") { + image.src = "data:image/jpeg;base64," + source; + canvas.width = shape[1]; + canvas.height = shape[0]; + } + + // Send the Acknowledgment Message + websocket_gui.send("#ack"); + } + } + } + +// For image object +image.onload = function () { + update_image(); +} + +// Request Animation Frame to remove the flickers +function update_image() { + animation_id = window.requestAnimationFrame(update_image); + context.clearRect(0, 0, canvas.width, canvas.height); + context.drawImage(image, 0, 0); +} diff --git a/static/exercises/visual_odometry_3D/utils/CBuffer.js b/static/exercises/visual_odometry_3D/utils/CBuffer.js new file mode 100644 index 000000000..bd392e628 --- /dev/null +++ b/static/exercises/visual_odometry_3D/utils/CBuffer.js @@ -0,0 +1,31 @@ +class CBuffer { + constructor(n) { + this._array = new Array(n); + this._start = 0; + this._end = 0; + this.length = 0; + this.size = n; + } + get(i) { + if (i < 0) return this.get(i + this.length); + if (i >= this.length) return this.get(i - this.length); + return this._array[i]; + } + push(item) { + this._array[this._end] = item; + this._end++; + if (this.length < this._array.length) this.length++; + if (this.length === this._array.length) this._start++; + if (this._start === this._array.length) this._start = 0; + if (this._end === this._array.length) this._end = 0; + } + getPoints() { + const points = []; + for (let i = this._start; i < this.length + this._start; i++) { + const v = this.get(i); + if (v !== undefined && v !== this.get(this._end)) + points.push(v); + } + return points; + } +} \ No newline at end of file diff --git a/static/exercises/visual_odometry_3D/vehicles/car.js b/static/exercises/visual_odometry_3D/vehicles/car.js new file mode 100644 index 000000000..8ce39d598 --- /dev/null +++ b/static/exercises/visual_odometry_3D/vehicles/car.js @@ -0,0 +1,37 @@ +function createWheels(scale) { + const geometry = new THREE.BoxBufferGeometry(12 * scale, 12 * scale, 33 * scale); + const material = new THREE.MeshLambertMaterial({ color: 0x333333 }); + const wheel = new THREE.Mesh(geometry, material); + return wheel; +} + +function createCar(scale = 1) { + const car = new THREE.Group(); + + const backWheel = createWheels(scale); + backWheel.position.y = 6 * scale; + backWheel.position.x = -18 * scale; + car.add(backWheel); + + const frontWheel = createWheels(scale); + frontWheel.position.y = 6 * scale; + frontWheel.position.x = 18 * scale; + car.add(frontWheel); + + const main = new THREE.Mesh( + new THREE.BoxBufferGeometry(60 * scale, 15 * scale, 30 * scale), + new THREE.MeshLambertMaterial({ color: 0x78b14b }) + ); + main.position.y = 12 * scale; + car.add(main); + + const cabin = new THREE.Mesh( + new THREE.BoxBufferGeometry(33 * scale, 12 * scale, 24 * scale), + new THREE.MeshLambertMaterial({ color: 0xffffff }) + ); + cabin.position.x = -6 * scale; + cabin.position.y = 25.5 * scale; + car.add(cabin); + + return car; +} \ No newline at end of file diff --git a/static/exercises/visual_odometry_3D/vehicles/frame.js b/static/exercises/visual_odometry_3D/vehicles/frame.js new file mode 100644 index 000000000..6499d634d --- /dev/null +++ b/static/exercises/visual_odometry_3D/vehicles/frame.js @@ -0,0 +1,50 @@ +function createLine(a, b, color = 0xff00ff) { + const material = new THREE.LineBasicMaterial({ color: color }); + + const points = []; + points.push(new THREE.Vector3(a[0], a[1], a[2])); + points.push(new THREE.Vector3(b[0], b[1], b[2])); + + const geometry = new THREE.BufferGeometry().setFromPoints(points); + const line = new THREE.Line(geometry, material); + + return line; +} + +function createFrame(width = 16, height = 9, scale = 1, color = 0xff0000) { + const frame = new THREE.Group(); + + // set camare center + const center0 = [0, 0, 0] + + // scales + scale *= 10; + const distance = .4 * scale + height = (height / width) * scale + width = scale + + // frame points + const pointA = [distance, height / 2, -width / 2] + const pointB = [distance, height / 2, width / 2] + const pointC = [distance, -height / 2, width / 2] + const pointD = [distance, -height / 2, -width / 2] + + const points = [pointA, pointB, pointC, pointD] + + // draw lines + // center to corner + for (let i = 0; i < points.length; ++i) { + frame.add(createLine(center0, points[i])) + } + + // corner to corner + for (let i = 1; i < points.length; ++i) { + frame.add(createLine(points[i - 1], points[i], color)) + } + frame.add(createLine(points[3], points[0], color)) + + // x edge guide + frame.add(createLine(center0, [distance * 4, 0, 0], 0xBDB76B)) + + return frame; +} From 021341eacee1b7754af3a25d2eed22586835cdef Mon Sep 17 00:00:00 2001 From: PabloAsensio Date: Fri, 20 Oct 2023 15:55:31 +0200 Subject: [PATCH 2/3] update API --- exercises/static/exercises/visual_odometry_3D/exercise.py | 2 +- exercises/static/exercises/visual_odometry_3D/hal.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/exercises/static/exercises/visual_odometry_3D/exercise.py b/exercises/static/exercises/visual_odometry_3D/exercise.py index 2c6b2b27c..fec43edaa 100644 --- a/exercises/static/exercises/visual_odometry_3D/exercise.py +++ b/exercises/static/exercises/visual_odometry_3D/exercise.py @@ -182,7 +182,7 @@ def generate_modules(self): hal_module.HAL.set_estimated_position = self.hal.set_estimated_position hal_module.HAL.set_estimated_euler_angles = self.hal.set_estimated_euler_angles - hal_module.HAL.get_true_euler_angles_corrected_array = self.hal.get_true_euler_angles_corrected_array # A BORRAR + hal_module.HAL.get_camera_model = self.hal.get_camera_model # Define GUI module gui_module = importlib.util.module_from_spec(importlib.machinery.ModuleSpec("GUI", None)) diff --git a/exercises/static/exercises/visual_odometry_3D/hal.py b/exercises/static/exercises/visual_odometry_3D/hal.py index 976946222..693d940e9 100644 --- a/exercises/static/exercises/visual_odometry_3D/hal.py +++ b/exercises/static/exercises/visual_odometry_3D/hal.py @@ -90,6 +90,9 @@ def __init__(self): self.estimated_position = np.array([0, 0, 0], dtype=np.float32).flatten().round(decimals=5) self.estimated_euler_angles = np.array([0, 0, 0], dtype=np.float32).flatten().round(decimals=5) + # camera + self.camera = PinholeCamera.from_kitti(file_path=CALIBRATION_FILE, width=1241, height=376) + # User method # Advance current frame def advance(self): @@ -206,4 +209,9 @@ def set_estimated_position(self, x: float, y: float, z: float): # Set estimated orientation in euler angles calculated by user def set_estimated_euler_angles(self, roll: float, pitch: float, yaw: float): self.estimated_euler_angles = np.array([roll, pitch, yaw], dtype=np.float32).flatten().round(decimals=5) + + # User method + # Return the pinhole camera model + def get_camera_model(self): + return self.camera \ No newline at end of file From 772e4dd4e59f112126475d41c449c2cc664b6d6f Mon Sep 17 00:00:00 2001 From: PabloAsensio Date: Fri, 20 Oct 2023 16:12:14 +0200 Subject: [PATCH 3/3] fix error --- .../exercises/visual_odometry_3D/interfaces/pinhole_camera.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py b/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py index c2fd5a835..639701a9c 100644 --- a/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py +++ b/exercises/static/exercises/visual_odometry_3D/interfaces/pinhole_camera.py @@ -17,7 +17,7 @@ def __init__(self, width=None, height=None, fu=None, fv=None, cu=None, cv=None, self.distortion_coefficients = distortion_coefficients self.extrinsics = extrinsics self.intrinsics = intrinsics - self.camera_matrix = self.cameraMatrix() + self.camera_matrix = self.get_camera_matrix() def get_camera_matrix(self):