Browse Source

Editor: first steps towards light gizmos.

For the moment handling just changes via UI, not yet possible to drag gizmos.
alteredq 12 years ago
parent
commit
dc93beb648
4 changed files with 132 additions and 27 deletions
  1. 2 0
      build/three.js
  2. 13 13
      build/three.min.js
  3. 115 14
      editor/js/ui/Viewport.js
  4. 2 0
      src/loaders/SceneLoader.js

+ 2 - 0
build/three.js

@@ -9602,6 +9602,8 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 			}
 
+			ta.object.target.properties.inverseTarget = ta.object;
+
 		}
 
 	};

+ 13 - 13
build/three.min.js

@@ -196,19 +196,19 @@ e.castShadow,g.receiveShadow=e.receiveShadow,a.add(g),P.objects[c]=g}else"Direct
 u[2])):"AmbientLight"===e.type&&(J=new THREE.AmbientLight(H)),a.add(J),J.name=c,P.lights[c]=J,P.objects[c]=J):"PerspectiveCamera"===e.type||"OrthographicCamera"===e.type?("PerspectiveCamera"===e.type?I=new THREE.PerspectiveCamera(e.fov,e.aspect,e.near,e.far):"OrthographicCamera"===e.type&&(I=new THREE.OrthographicCamera(w.left,w.right,w.top,w.bottom,w.near,w.far)),u=e.position,I.position.set(u[0],u[1],u[2]),a.add(I),I.name=c,P.cameras[c]=I,P.objects[c]=I):(u=e.position,t=e.rotation,F=e.quaternion,
 A=e.scale,F=0,g=new THREE.Object3D,g.name=c,g.position.set(u[0],u[1],u[2]),F?(g.quaternion.set(F[0],F[1],F[2],F[3]),g.useQuaternion=!0):g.rotation.set(t[0],t[1],t[2]),g.scale.set(A[0],A[1],A[2]),g.visible=void 0!==e.visible?e.visible:!1,a.add(g),P.objects[c]=g,P.empties[c]=g);if(g){if(void 0!==e.properties)for(var n in e.properties)g.properties[n]=e.properties[n];void 0!==e.children&&f(g,e.children)}}}function g(a){return function(b){P.geometries[a]=b;e();N-=1;m.onLoadComplete();j()}}function h(a,
 b,c,d){return function(f){var f=f.content?f.content:f.dae?f.scene:f,g=d.position,h=d.rotation,i=d.quaternion,l=d.scale;f.position.set(g[0],g[1],g[2]);i?(f.quaternion.set(i[0],i[1],i[2],i[3]),f.useQuaternion=!0):f.rotation.set(h[0],h[1],h[2]);f.scale.set(l[0],l[1],l[2]);c&&f.traverse(function(a){a.material=c});b.add(f);P.objects[a]=f;e();N-=1;m.onLoadComplete();j()}}function i(a){return function(b){P.geometries[a]=b}}function j(){m.callbackProgress({totalModels:Q,totalTextures:ea,loadedModels:Q-N,
-loadedTextures:ea-L},P);m.onLoadProgress();if(0===N&&0===L){for(var a=0;a<M.length;a++){var c=M[a],d=P.objects[c.targetName];d?c.object.target=d:(c.object.target=new THREE.Object3D,P.scene.add(c.object.target))}b(P)}}var m=this,n=THREE.Loader.prototype.extractUrlBase(c),l,p,o,q,r,s,z,u,t,F,A,w,v,C,D,I,K,B,J,H,G,N,L,Q,ea,P,M=[],R=a,ba;for(ba in this.geometryHandlerMap)a=this.geometryHandlerMap[ba].loaderClass,this.geometryHandlerMap[ba].loaderObject=new a;for(ba in this.hierarchyHandlerMap)a=this.hierarchyHandlerMap[ba].loaderClass,
-this.hierarchyHandlerMap[ba].loaderObject=new a;L=N=0;P={scene:new THREE.Scene,geometries:{},materials:{},textures:{},objects:{},cameras:{},lights:{},fogs:{},empties:{}};if(R.transform&&(ba=R.transform.position,a=R.transform.rotation,c=R.transform.scale,ba&&P.scene.position.set(ba[0],ba[1],ba[2]),a&&P.scene.rotation.set(a[0],a[1],a[2]),c&&P.scene.scale.set(c[0],c[1],c[2]),ba||a||c))P.scene.updateMatrix(),P.scene.updateMatrixWorld();ba=function(a){return function(){L-=a;j();m.onLoadComplete()}};for(o in R.fogs)a=
-R.fogs[o],"linear"===a.type?K=new THREE.Fog(0,a.near,a.far):"exp2"===a.type&&(K=new THREE.FogExp2(0,a.density)),w=a.color,K.color.setRGB(w[0],w[1],w[2]),P.fogs[o]=K;for(l in R.geometries)r=R.geometries[l],r.type in this.geometryHandlerMap&&(N+=1,m.onLoadStart());for(var ja in R.objects)o=R.objects[ja],o.type&&o.type in this.hierarchyHandlerMap&&(N+=1,m.onLoadStart());Q=N;for(l in R.geometries)if(r=R.geometries[l],"cube"===r.type)C=new THREE.CubeGeometry(r.width,r.height,r.depth,r.widthSegments,r.heightSegments,
-r.depthSegments,null,r.flipped,r.sides),P.geometries[l]=C;else if("plane"===r.type)C=new THREE.PlaneGeometry(r.width,r.height,r.widthSegments,r.heightSegments),P.geometries[l]=C;else if("sphere"===r.type)C=new THREE.SphereGeometry(r.radius,r.widthSegments,r.heightSegments),P.geometries[l]=C;else if("cylinder"===r.type)C=new THREE.CylinderGeometry(r.topRad,r.botRad,r.height,r.radSegs,r.heightSegs),P.geometries[l]=C;else if("torus"===r.type)C=new THREE.TorusGeometry(r.radius,r.tube,r.segmentsR,r.segmentsT),
-P.geometries[l]=C;else if("icosahedron"===r.type)C=new THREE.IcosahedronGeometry(r.radius,r.subdivisions),P.geometries[l]=C;else if(r.type in this.geometryHandlerMap){ja={};for(B in r)"type"!==B&&"url"!==B&&(ja[B]=r[B]);this.geometryHandlerMap[r.type].loaderObject.load(d(r.url,R.urlBaseType),g(l),ja)}else"embedded"===r.type&&(ja=R.embeds[r.id],ja.metadata=R.metadata,ja&&this.geometryHandlerMap.ascii.loaderObject.createModel(ja,i(l),""));for(q in R.textures)if(l=R.textures[q],l.url instanceof Array){L+=
-l.url.length;for(B=0;B<l.url.length;B++)m.onLoadStart()}else L+=1,m.onLoadStart();ea=L;for(q in R.textures){l=R.textures[q];void 0!==l.mapping&&void 0!==THREE[l.mapping]&&(l.mapping=new THREE[l.mapping]);if(l.url instanceof Array){ja=l.url.length;o=[];for(B=0;B<ja;B++)o[B]=d(l.url[B],R.urlBaseType);B=(B=o[0].endsWith(".dds"))?THREE.ImageUtils.loadCompressedTextureCube(o,l.mapping,ba(ja)):THREE.ImageUtils.loadTextureCube(o,l.mapping,ba(ja))}else{B=l.url.toLowerCase().endsWith(".dds");ja=d(l.url,R.urlBaseType);
-o=ba(1);B=B?THREE.ImageUtils.loadCompressedTexture(ja,l.mapping,o):THREE.ImageUtils.loadTexture(ja,l.mapping,o);void 0!==THREE[l.minFilter]&&(B.minFilter=THREE[l.minFilter]);void 0!==THREE[l.magFilter]&&(B.magFilter=THREE[l.magFilter]);l.anisotropy&&(B.anisotropy=l.anisotropy);if(l.repeat&&(B.repeat.set(l.repeat[0],l.repeat[1]),1!==l.repeat[0]&&(B.wrapS=THREE.RepeatWrapping),1!==l.repeat[1]))B.wrapT=THREE.RepeatWrapping;l.offset&&B.offset.set(l.offset[0],l.offset[1]);if(l.wrap&&(ja={repeat:THREE.RepeatWrapping,
-mirror:THREE.MirroredRepeatWrapping},void 0!==ja[l.wrap[0]]&&(B.wrapS=ja[l.wrap[0]]),void 0!==ja[l.wrap[1]]))B.wrapT=ja[l.wrap[1]]}P.textures[q]=B}for(p in R.materials){s=R.materials[p];for(v in s.parameters)"envMap"===v||"map"===v||"lightMap"===v||"bumpMap"===v?s.parameters[v]=P.textures[s.parameters[v]]:"shading"===v?s.parameters[v]="flat"===s.parameters[v]?THREE.FlatShading:THREE.SmoothShading:"side"===v?s.parameters[v]="double"==s.parameters[v]?THREE.DoubleSide:"back"==s.parameters[v]?THREE.BackSide:
-THREE.FrontSide:"blending"===v?s.parameters[v]=s.parameters[v]in THREE?THREE[s.parameters[v]]:THREE.NormalBlending:"combine"===v?s.parameters[v]="MixOperation"==s.parameters[v]?THREE.MixOperation:THREE.MultiplyOperation:"vertexColors"===v?"face"==s.parameters[v]?s.parameters[v]=THREE.FaceColors:s.parameters[v]&&(s.parameters[v]=THREE.VertexColors):"wrapRGB"===v&&(q=s.parameters[v],s.parameters[v]=new THREE.Vector3(q[0],q[1],q[2]));void 0!==s.parameters.opacity&&1>s.parameters.opacity&&(s.parameters.transparent=
-!0);s.parameters.normalMap?(q=THREE.ShaderUtils.lib.normal,ba=THREE.UniformsUtils.clone(q.uniforms),l=s.parameters.color,B=s.parameters.specular,ja=s.parameters.ambient,o=s.parameters.shininess,ba.tNormal.value=P.textures[s.parameters.normalMap],s.parameters.normalScale&&ba.uNormalScale.value.set(s.parameters.normalScale[0],s.parameters.normalScale[1]),s.parameters.map&&(ba.tDiffuse.value=s.parameters.map,ba.enableDiffuse.value=!0),s.parameters.envMap&&(ba.tCube.value=s.parameters.envMap,ba.enableReflection.value=
-!0,ba.uReflectivity.value=s.parameters.reflectivity),s.parameters.lightMap&&(ba.tAO.value=s.parameters.lightMap,ba.enableAO.value=!0),s.parameters.specularMap&&(ba.tSpecular.value=P.textures[s.parameters.specularMap],ba.enableSpecular.value=!0),s.parameters.displacementMap&&(ba.tDisplacement.value=P.textures[s.parameters.displacementMap],ba.enableDisplacement.value=!0,ba.uDisplacementBias.value=s.parameters.displacementBias,ba.uDisplacementScale.value=s.parameters.displacementScale),ba.uDiffuseColor.value.setHex(l),
-ba.uSpecularColor.value.setHex(B),ba.uAmbientColor.value.setHex(ja),ba.uShininess.value=o,s.parameters.opacity&&(ba.uOpacity.value=s.parameters.opacity),D=new THREE.ShaderMaterial({fragmentShader:q.fragmentShader,vertexShader:q.vertexShader,uniforms:ba,lights:!0,fog:!0})):D=new THREE[s.type](s.parameters);P.materials[p]=D}e();P.cameras&&R.defaults.camera&&(P.currentCamera=P.cameras[R.defaults.camera]);P.fogs&&R.defaults.fog&&(P.scene.fog=P.fogs[R.defaults.fog]);w=R.defaults.bgcolor;P.bgColor=new THREE.Color;
-P.bgColor.setRGB(w[0],w[1],w[2]);P.bgColorAlpha=R.defaults.bgalpha;m.callbackSync(P);j()};THREE.TextureLoader=function(){THREE.EventTarget.call(this);this.crossOrigin=null};
+loadedTextures:ea-L},P);m.onLoadProgress();if(0===N&&0===L){for(var a=0;a<M.length;a++){var c=M[a],d=P.objects[c.targetName];d?c.object.target=d:(c.object.target=new THREE.Object3D,P.scene.add(c.object.target));c.object.target.properties.inverseTarget=c.object}b(P)}}var m=this,n=THREE.Loader.prototype.extractUrlBase(c),l,p,o,q,r,s,z,u,t,F,A,w,v,C,D,I,K,B,J,H,G,N,L,Q,ea,P,M=[],R=a,ba;for(ba in this.geometryHandlerMap)a=this.geometryHandlerMap[ba].loaderClass,this.geometryHandlerMap[ba].loaderObject=
+new a;for(ba in this.hierarchyHandlerMap)a=this.hierarchyHandlerMap[ba].loaderClass,this.hierarchyHandlerMap[ba].loaderObject=new a;L=N=0;P={scene:new THREE.Scene,geometries:{},materials:{},textures:{},objects:{},cameras:{},lights:{},fogs:{},empties:{}};if(R.transform&&(ba=R.transform.position,a=R.transform.rotation,c=R.transform.scale,ba&&P.scene.position.set(ba[0],ba[1],ba[2]),a&&P.scene.rotation.set(a[0],a[1],a[2]),c&&P.scene.scale.set(c[0],c[1],c[2]),ba||a||c))P.scene.updateMatrix(),P.scene.updateMatrixWorld();
+ba=function(a){return function(){L-=a;j();m.onLoadComplete()}};for(o in R.fogs)a=R.fogs[o],"linear"===a.type?K=new THREE.Fog(0,a.near,a.far):"exp2"===a.type&&(K=new THREE.FogExp2(0,a.density)),w=a.color,K.color.setRGB(w[0],w[1],w[2]),P.fogs[o]=K;for(l in R.geometries)r=R.geometries[l],r.type in this.geometryHandlerMap&&(N+=1,m.onLoadStart());for(var ja in R.objects)o=R.objects[ja],o.type&&o.type in this.hierarchyHandlerMap&&(N+=1,m.onLoadStart());Q=N;for(l in R.geometries)if(r=R.geometries[l],"cube"===
+r.type)C=new THREE.CubeGeometry(r.width,r.height,r.depth,r.widthSegments,r.heightSegments,r.depthSegments,null,r.flipped,r.sides),P.geometries[l]=C;else if("plane"===r.type)C=new THREE.PlaneGeometry(r.width,r.height,r.widthSegments,r.heightSegments),P.geometries[l]=C;else if("sphere"===r.type)C=new THREE.SphereGeometry(r.radius,r.widthSegments,r.heightSegments),P.geometries[l]=C;else if("cylinder"===r.type)C=new THREE.CylinderGeometry(r.topRad,r.botRad,r.height,r.radSegs,r.heightSegs),P.geometries[l]=
+C;else if("torus"===r.type)C=new THREE.TorusGeometry(r.radius,r.tube,r.segmentsR,r.segmentsT),P.geometries[l]=C;else if("icosahedron"===r.type)C=new THREE.IcosahedronGeometry(r.radius,r.subdivisions),P.geometries[l]=C;else if(r.type in this.geometryHandlerMap){ja={};for(B in r)"type"!==B&&"url"!==B&&(ja[B]=r[B]);this.geometryHandlerMap[r.type].loaderObject.load(d(r.url,R.urlBaseType),g(l),ja)}else"embedded"===r.type&&(ja=R.embeds[r.id],ja.metadata=R.metadata,ja&&this.geometryHandlerMap.ascii.loaderObject.createModel(ja,
+i(l),""));for(q in R.textures)if(l=R.textures[q],l.url instanceof Array){L+=l.url.length;for(B=0;B<l.url.length;B++)m.onLoadStart()}else L+=1,m.onLoadStart();ea=L;for(q in R.textures){l=R.textures[q];void 0!==l.mapping&&void 0!==THREE[l.mapping]&&(l.mapping=new THREE[l.mapping]);if(l.url instanceof Array){ja=l.url.length;o=[];for(B=0;B<ja;B++)o[B]=d(l.url[B],R.urlBaseType);B=(B=o[0].endsWith(".dds"))?THREE.ImageUtils.loadCompressedTextureCube(o,l.mapping,ba(ja)):THREE.ImageUtils.loadTextureCube(o,
+l.mapping,ba(ja))}else{B=l.url.toLowerCase().endsWith(".dds");ja=d(l.url,R.urlBaseType);o=ba(1);B=B?THREE.ImageUtils.loadCompressedTexture(ja,l.mapping,o):THREE.ImageUtils.loadTexture(ja,l.mapping,o);void 0!==THREE[l.minFilter]&&(B.minFilter=THREE[l.minFilter]);void 0!==THREE[l.magFilter]&&(B.magFilter=THREE[l.magFilter]);l.anisotropy&&(B.anisotropy=l.anisotropy);if(l.repeat&&(B.repeat.set(l.repeat[0],l.repeat[1]),1!==l.repeat[0]&&(B.wrapS=THREE.RepeatWrapping),1!==l.repeat[1]))B.wrapT=THREE.RepeatWrapping;
+l.offset&&B.offset.set(l.offset[0],l.offset[1]);if(l.wrap&&(ja={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==ja[l.wrap[0]]&&(B.wrapS=ja[l.wrap[0]]),void 0!==ja[l.wrap[1]]))B.wrapT=ja[l.wrap[1]]}P.textures[q]=B}for(p in R.materials){s=R.materials[p];for(v in s.parameters)"envMap"===v||"map"===v||"lightMap"===v||"bumpMap"===v?s.parameters[v]=P.textures[s.parameters[v]]:"shading"===v?s.parameters[v]="flat"===s.parameters[v]?THREE.FlatShading:THREE.SmoothShading:"side"===
+v?s.parameters[v]="double"==s.parameters[v]?THREE.DoubleSide:"back"==s.parameters[v]?THREE.BackSide:THREE.FrontSide:"blending"===v?s.parameters[v]=s.parameters[v]in THREE?THREE[s.parameters[v]]:THREE.NormalBlending:"combine"===v?s.parameters[v]="MixOperation"==s.parameters[v]?THREE.MixOperation:THREE.MultiplyOperation:"vertexColors"===v?"face"==s.parameters[v]?s.parameters[v]=THREE.FaceColors:s.parameters[v]&&(s.parameters[v]=THREE.VertexColors):"wrapRGB"===v&&(q=s.parameters[v],s.parameters[v]=new THREE.Vector3(q[0],
+q[1],q[2]));void 0!==s.parameters.opacity&&1>s.parameters.opacity&&(s.parameters.transparent=!0);s.parameters.normalMap?(q=THREE.ShaderUtils.lib.normal,ba=THREE.UniformsUtils.clone(q.uniforms),l=s.parameters.color,B=s.parameters.specular,ja=s.parameters.ambient,o=s.parameters.shininess,ba.tNormal.value=P.textures[s.parameters.normalMap],s.parameters.normalScale&&ba.uNormalScale.value.set(s.parameters.normalScale[0],s.parameters.normalScale[1]),s.parameters.map&&(ba.tDiffuse.value=s.parameters.map,
+ba.enableDiffuse.value=!0),s.parameters.envMap&&(ba.tCube.value=s.parameters.envMap,ba.enableReflection.value=!0,ba.uReflectivity.value=s.parameters.reflectivity),s.parameters.lightMap&&(ba.tAO.value=s.parameters.lightMap,ba.enableAO.value=!0),s.parameters.specularMap&&(ba.tSpecular.value=P.textures[s.parameters.specularMap],ba.enableSpecular.value=!0),s.parameters.displacementMap&&(ba.tDisplacement.value=P.textures[s.parameters.displacementMap],ba.enableDisplacement.value=!0,ba.uDisplacementBias.value=
+s.parameters.displacementBias,ba.uDisplacementScale.value=s.parameters.displacementScale),ba.uDiffuseColor.value.setHex(l),ba.uSpecularColor.value.setHex(B),ba.uAmbientColor.value.setHex(ja),ba.uShininess.value=o,s.parameters.opacity&&(ba.uOpacity.value=s.parameters.opacity),D=new THREE.ShaderMaterial({fragmentShader:q.fragmentShader,vertexShader:q.vertexShader,uniforms:ba,lights:!0,fog:!0})):D=new THREE[s.type](s.parameters);P.materials[p]=D}e();P.cameras&&R.defaults.camera&&(P.currentCamera=P.cameras[R.defaults.camera]);
+P.fogs&&R.defaults.fog&&(P.scene.fog=P.fogs[R.defaults.fog]);w=R.defaults.bgcolor;P.bgColor=new THREE.Color;P.bgColor.setRGB(w[0],w[1],w[2]);P.bgColorAlpha=R.defaults.bgalpha;m.callbackSync(P);j()};THREE.TextureLoader=function(){THREE.EventTarget.call(this);this.crossOrigin=null};
 THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a){var b=this,c=new Image;c.addEventListener("load",function(){var a=new THREE.Texture(c);a.needsUpdate=!0;b.dispatchEvent({type:"load",content:a})},!1);c.addEventListener("error",function(){b.dispatchEvent({type:"error",message:"Couldn't load URL ["+a+"]"})},!1);b.crossOrigin&&(c.crossOrigin=b.crossOrigin);c.src=a}};
 THREE.Material=function(){THREE.MaterialLibrary.push(this);this.id=THREE.MaterialIdCount++;this.name="";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.depthWrite=this.depthTest=!0;this.polygonOffset=!1;this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=0;this.overdraw=!1;this.needsUpdate=this.visible=!0};
 THREE.Material.prototype.setValues=function(a){if(void 0!==a)for(var b in a){var c=a[b];if(void 0===c)console.warn("THREE.Material: '"+b+"' parameter is undefined.");else if(b in this){var d=this[b];d instanceof THREE.Color&&c instanceof THREE.Color?d.copy(c):d instanceof THREE.Color&&"number"===typeof c?d.setHex(c):d instanceof THREE.Vector3&&c instanceof THREE.Vector3?d.copy(c):this[b]=c}}};

+ 115 - 14
editor/js/ui/Viewport.js

@@ -7,7 +7,7 @@ var Viewport = function ( signals ) {
 
 	var enableHelpersFog = true;
 
-	//
+	// helpers
 
 	var objects = [];
 
@@ -49,6 +49,13 @@ var Viewport = function ( signals ) {
 
 	//
 
+	var lightGeo = new THREE.SphereGeometry( 5, 16, 8 );
+	var targetGeo = new THREE.SphereGeometry( 5, 8, 4 );
+
+	var direction = new THREE.Vector3();
+
+	//
+
 	var scene = new THREE.Scene();
 
 	var camera = new THREE.PerspectiveCamera( 50, 1, 1, 5000 );
@@ -57,12 +64,13 @@ var Viewport = function ( signals ) {
 	scene.add( camera );
 
 	var light1 = new THREE.DirectionalLight( 0xffffff );
-	light1.position.set( 1, 0.5, 0 ).normalize();
-	scene.add( light1 );
+	light1.position.set( 1, 0.5, 0 ).multiplyScalar( 400 );
 
 	var light2 = new THREE.DirectionalLight( 0xffffff, 0.5 );
-	light2.position.set( - 1, - 0.5, 0 ).normalize();
-	scene.add( light2 );
+	light2.position.set( - 1, - 0.5, 0 ).multiplyScalar( 400 );
+
+	light1.target.properties.targetInverse = light1;
+	light2.target.properties.targetInverse = light2;
 
 	// fog
 
@@ -81,7 +89,6 @@ var Viewport = function ( signals ) {
 
 	light2.name = "Light 2";
 	light2.target.name = "Light 2 Target";
-	signals.sceneChanged.dispatch( scene );
 
 	// active objects
 
@@ -262,16 +269,65 @@ var Viewport = function ( signals ) {
 
 	} );
 
+	var handleAddition = function ( object ) {
 
-	// signals
+		// add to picking list
 
-	signals.objectAdded.add( function ( object ) {
+		objects.push( object );
 
-		object.traverse( function ( child ) {
+		// create helpers for invisible object types (lights, cameras, targets)
 
-			objects.push( child );
+		if ( object instanceof THREE.DirectionalLight ) {
 
-		} );
+			var lightGizmo = new THREE.Object3D();
+			lightGizmo.position = object.position;
+			lightGizmo.properties.gizmo = true;
+
+			var lightColor = object.color.clone();
+			lightColor.r *= object.intensity;
+			lightColor.g *= object.intensity;
+			lightColor.b *= object.intensity;
+
+			var length = 30;
+			direction.sub( object.target.position, object.position );
+
+			var lightArrow = new THREE.ArrowHelper( direction, null, length, lightColor.getHex() );
+
+			var lightMaterial = new THREE.MeshBasicMaterial( { color: lightColor.getHex() } );
+			var lightSphere = new THREE.Mesh( lightGeo, lightMaterial );
+
+			lightGizmo.add( lightArrow );
+			lightGizmo.add( lightSphere );
+
+			sceneHelpers.add( lightGizmo );
+
+			object.properties.arrow = lightArrow;
+
+			if ( object.target.properties.targetInverse ) {
+
+				var targetMaterial = new THREE.MeshBasicMaterial( { color: lightColor.getHex(), wireframe: true } );
+
+				var targetSphere = new THREE.Mesh( targetGeo, targetMaterial );
+				targetSphere.position = object.target.position;
+				targetSphere.properties.gizmo = true;
+
+				sceneHelpers.add( targetSphere );
+
+			}
+
+		} else if ( object instanceof THREE.PointLight ) {
+		} else if ( object instanceof THREE.SpotLight ) {
+		} else if ( object instanceof THREE.HemisphereLight ) {
+		}
+
+	};
+
+
+	// signals
+
+	signals.objectAdded.add( function ( object ) {
+
+		object.traverse( handleAddition );
 
 		scene.add( object );
 		render();
@@ -286,6 +342,19 @@ var Viewport = function ( signals ) {
 
 			object.updateProjectionMatrix();
 
+		} else if ( object instanceof THREE.DirectionalLight ) {
+
+			direction.sub( object.target.position, object.position );
+			object.properties.arrow.setDirection( direction );
+
+		} else if ( object instanceof THREE.PointLight ) {
+		} else if ( object instanceof THREE.SpotLight ) {
+		} else if ( object instanceof THREE.HemisphereLight ) {
+		} else if ( object.properties.targetInverse ) {
+
+			direction.sub( object.position, object.properties.targetInverse.position );
+			object.properties.targetInverse.properties.arrow.setDirection( direction );
+
 		}
 
 		render();
@@ -304,6 +373,8 @@ var Viewport = function ( signals ) {
 
 		}
 
+		// remove from picking list
+
 		var toRemove = {};
 
 		selected.traverse( function ( child ) {
@@ -328,6 +399,8 @@ var Viewport = function ( signals ) {
 
 		objects = newObjects;
 
+		//
+
 		selectionBox.visible = false;
 		selectionAxis.visible = false;
 
@@ -523,14 +596,36 @@ var Viewport = function ( signals ) {
 
 		scene = newScene;
 
-		objects = [];
+		// remove old gizmos
 
-		scene.traverse( function ( child ) {
+		var toRemove = {};
 
-			objects.push( child );
+		sceneHelpers.traverse( function ( child ) {
+
+			if ( child.properties.gizmo ) {
+
+				toRemove[ child.id ] = child;
+
+			}
 
 		} );
 
+		for ( var id in toRemove ) {
+
+			sceneHelpers.remove( toRemove[ id ] );
+
+		}
+
+		// reset picking list
+
+		objects = [];
+
+		// add new gizmos and fill picking list
+
+		scene.traverse( handleAddition );
+
+		//
+
 		if ( newCamera ) {
 
 			camera = newCamera;
@@ -601,6 +696,12 @@ var Viewport = function ( signals ) {
 	container.dom.style.outline = 'transparent';
 	container.dom.addEventListener( 'keydown', onKeyDown, false );
 
+	// must come after listeners are registered
+
+	signals.sceneChanged.dispatch( scene );
+	signals.objectAdded.dispatch( light1 );
+	signals.objectAdded.dispatch( light2 );
+
 	//
 
 	function updateMaterials( root ) {

+ 2 - 0
src/loaders/SceneLoader.js

@@ -642,6 +642,8 @@ THREE.SceneLoader.prototype.parse = function ( json, callbackFinished, url ) {
 
 			}
 
+			ta.object.target.properties.inverseTarget = ta.object;
+
 		}
 
 	};