1# -*- coding: utf-8 -*-
2# ####################################################################
3#  Copyright (C) 2005-2019 by the FIFE team
4#  http://www.fifengine.net
5#  This file is part of FIFE.
6#
7#  FIFE is free software; you can redistribute it and/or
8#  modify it under the terms of the GNU Lesser General Public
9#  License as published by the Free Software Foundation; either
10#  version 2.1 of the License, or (at your option) any later version.
11#
12#  This library is distributed in the hope that it will be useful,
13#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15#  Lesser General Public License for more details.
16#
17#  You should have received a copy of the GNU Lesser General Public
18#  License along with this library; if not, write to the
19#  Free Software Foundation, Inc.,
20#  51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21# ####################################################################
22
23""" main xml parser class for xml map loading """
24from __future__ import print_function
25
26from builtins import str
27from builtins import object
28import time
29
30from fife import fife
31
32from fife.extensions.serializers import ET
33from fife.extensions.serializers import SerializerError, InvalidFormat
34from fife.extensions.serializers import NameClash, NotFound, WrongFileType
35
36from fife.extensions.serializers.xmlobject import XMLObjectLoader
37from fife.extensions.serializers.xmlanimation import loadXMLAnimation
38from fife.extensions.serializers.xml_loader_tools import loadImportFile, loadImportDir
39from fife.extensions.serializers.xml_loader_tools import loadImportDirRec
40from fife.extensions.serializers.xml_loader_tools import root_subfile, reverse_root_subfile
41
42
43FORMAT = '1.0'
44
45class XMLMapLoader(object):
46	""" The B{XMLMapLoader} parses the xml map using several section.
47	Each section fires a callback (if given) which can e. g. be
48	used to show a progress bar.
49
50	The callback sends two values, a string and a float (which shows
51	the overall process): callback(string, float)
52	"""
53	def __init__(self, engine, callback, debug, extensions):
54		"""
55		@type	engine:		object
56		@param	engine:		a pointer to fife.engine
57		@type	callback:	function
58		@param	callback:	a callback with two arguments, optional
59		@type	debug:		bool
60		@param	debug:		flag to activate / deactivate print statements
61		@type	extensions:	dict
62		@param	extensions:	information package which extension should be activated (lights, sounds)
63		"""
64#		self.thisown = 0
65
66		self.callback = callback
67		self.debug = debug
68
69		self.engine = engine
70		self.vfs = self.engine.getVFS()
71		self.model = self.engine.getModel()
72		self.image_manager = self.engine.getImageManager()
73		self.anim_pool = None
74
75		self.obj_loader = XMLObjectLoader(engine)
76
77		self.map = None
78		self.source = None
79		self.time_to_load = 0
80
81		self.nspace = None
82
83		self.msg = {}
84		self.msg['map'] = 'created map'
85		self.msg['imports'] = 'loaded imports'
86		self.msg['layer'] = 'loaded layer: %s'
87		self.msg['camera'] = 'loaded camera: %s'
88
89		if 'sound' not in extensions:
90			extensions['sound'] = False
91		if 'lights' not in extensions:
92			extensions['lights'] = False
93
94		self.light_data = {}
95		self.extensions = extensions
96
97	def _err(self, msg):
98		raise SyntaxError(''.join(['File: ', self.source, ' . ', msg]))
99
100	def loadResource(self, location):
101		""" overwrite of B{fife.ResourceLoader}
102
103		@type	location:	object
104		@param	location:	path to a map file as a fife.ResourceLocation
105		@return	FIFE map object
106		@rtype	object
107		"""
108		start_time = time.time()
109		self.source = location
110		f = self.vfs.open(self.source)
111		f.thisown = 1
112		tree = ET.parse(f)
113		root = tree.getroot()
114
115		map = self.parse_map(root)
116		self.time_to_load = time.time() - start_time
117		return map
118
119	def parse_map(self, mapelt):
120		""" start parsing the xml structure and
121		call submethods for turning found tags
122		into FIFE objects and create the map
123
124		@type	mapelt:	object
125		@param	mapelt:	ElementTree root
126		@return	FIFE map object
127		@rtype	object
128		"""
129		if not mapelt:
130			self._err('No <map> element found at top level of map file definition.')
131		_id, format = mapelt.get('id'), mapelt.get('format')
132
133		if not format == FORMAT: self._err(''.join(['This file has format ', format, ' but this loader has format ', FORMAT]))
134		if not _id: self._err('Map declared without an identifier.')
135
136		map = None
137		try:
138			self.map = self.model.createMap(str(_id))
139			self.map.setFilename(self.source)
140		except fife.Exception as e: # NameClash appears as general fife.Exception; any ideas?
141			print(e.getMessage())
142			print(''.join(['File: ', self.source, '. The map ', str(_id), ' already exists! Ignoring map definition.']))
143			return map
144
145		# xml-specific directory imports. This is used by xml savers.
146		self.map.importDirs = []
147
148		if self.callback is not None:
149			self.callback(self.msg['map'], float(0.25) )
150
151		self.parse_imports(mapelt, self.map)
152		self.parse_layers(mapelt, self.map)
153		self.parse_cameras(mapelt, self.map)
154
155		# create light nodes
156		if self.light_data:
157			self.create_light_nodes(self.map)
158
159		return self.map
160
161	def parse_imports(self, mapelt, map):
162		""" load all objects defined as import into memory
163
164		@type	mapelt:	object
165		@param	mapelt:	ElementTree root
166		@return	FIFE map object
167		@rtype	object
168		"""
169		parsedImports = {}
170
171		if self.callback:
172			tmplist = mapelt.findall('import')
173			i = float(0)
174
175		for item in mapelt.findall('import'):
176			_file = item.get('file')
177			if _file:
178				_file = reverse_root_subfile(self.source, _file)
179			_dir = item.get('dir')
180			if _dir:
181				_dir = reverse_root_subfile(self.source, _dir)
182
183			# Don't parse duplicate imports
184			if (_dir,_file) in parsedImports:
185				if self.debug: print("Duplicate import:" ,(_dir, _file))
186				continue
187			parsedImports[(_dir,_file)] = 1
188
189			if _file and _dir:
190				loadImportFile(self.obj_loader, '/'.join(_dir, _file), self.engine, self.debug)
191			elif _file:
192				loadImportFile(self.obj_loader, _file, self.engine, self.debug)
193			elif _dir:
194				loadImportDirRec(self.obj_loader, _dir, self.engine, self.debug)
195				map.importDirs.append(_dir)
196			else:
197				if self.debug: print('Empty import statement?')
198
199			if self.callback:
200				i += 1
201				self.callback(self.msg['imports'], float( i / float(len(tmplist)) * 0.25 + 0.25 ) )
202
203	def parse_layers(self, mapelt, map):
204		""" create all layers and their instances
205
206		@type	mapelt:	object
207		@param	mapelt:	ElementTree root
208		@type	map:	object
209		@param	map:	FIFE map object
210		"""
211		if self.callback is not None:
212			tmplist = mapelt.findall('layer')
213			i = float(0)
214
215		for layer in mapelt.findall('layer'):
216			_id = layer.get('id')
217			grid_type = layer.get('grid_type')
218
219			if not _id: self._err('<layer> declared with no id attribute.')
220			if not grid_type: self._err(''.join(['Layer ', str(_id), ' has no grid_type attribute.']))
221
222			x_scale = layer.get('x_scale')
223			y_scale = layer.get('y_scale')
224			rotation = layer.get('rotation')
225			x_offset = layer.get('x_offset')
226			y_offset = layer.get('y_offset')
227			z_offset = layer.get('z_offset')
228			pathing = layer.get('pathing')
229			transparency = layer.get('transparency')
230
231			layer_type = layer.get('layer_type')
232			layer_type_id = layer.get('layer_type_id')
233
234			if not x_scale: x_scale = 1.0
235			if not y_scale: y_scale = 1.0
236			if not rotation: rotation = 0.0
237			if not x_offset: x_offset = 0.0
238			if not y_offset: y_offset = 0.0
239			if not z_offset: z_offset = 0.0
240			if not pathing: pathing = "cell_edges_only"
241			if not transparency:
242				transparency = 0
243			else:
244				transparency = int(transparency)
245
246			cellgrid = self.model.getCellGrid(grid_type)
247			if not cellgrid: self._err('<layer> declared with invalid cellgrid type. (%s)' % grid_type)
248
249			cellgrid.setRotation(float(rotation))
250			cellgrid.setXScale(float(x_scale))
251			cellgrid.setYScale(float(y_scale))
252			cellgrid.setXShift(float(x_offset))
253			cellgrid.setYShift(float(y_offset))
254			cellgrid.setZShift(float(z_offset))
255
256			layer_obj = None
257			try:
258				layer_obj = map.createLayer(str(_id), cellgrid)
259			except fife.Exception as e:
260				print(e.getMessage())
261				print('The layer ' + str(_id) + ' already exists! Ignoring this layer.')
262
263				continue
264
265			strgy = fife.CELL_EDGES_ONLY
266			if pathing == "cell_edges_and_diagonals":
267				strgy = fife.CELL_EDGES_AND_DIAGONALS
268
269			layer_obj.setPathingStrategy(strgy)
270			layer_obj.setLayerTransparency(transparency)
271
272			if layer_type:
273				if layer_type == 'walkable':
274					layer_obj.setWalkable(True)
275				elif layer_type == 'interact' and layer_type_id:
276					layer_obj.setInteract(True, layer_type_id)
277
278			self.parse_instances(layer, layer_obj)
279
280			if self.extensions['lights']:
281				self.parse_lights(layer, layer_obj)
282			if self.extensions['sound']:
283				self.parse_sounds(layer, layer_obj)
284
285			if self.callback is not None:
286				i += 1
287				self.callback(self.msg['layer'] % str(_id), float( i / float(len(tmplist)) * 0.25 + 0.5 ) )
288
289
290		layers = map.getLayers()
291		for l in layers:
292			if l.isInteract():
293				walk_layer = map.getLayer(l.getWalkableId())
294				if walk_layer:
295					walk_layer.addInteractLayer(l);
296
297		for l in layers:
298			if l.isWalkable():
299				l.createCellCache()
300
301		# cleanup
302		if self.callback is not None:
303			del tmplist
304			del i
305
306	def parse_lights(self, layerelt, layer):
307		""" create light nodes
308
309		@type	layerelt:	object
310		@param	layerelt:	ElementTree layer branch
311		@type	layer:	object
312		@param	layer:	FIFE layer object
313		"""
314		_LIGHT_DEFAULT_BLENDING_SRC = -1
315		_LIGHT_DEFAULT_BLENDING_DST = -1
316		_LIGHT_DEFAULT_SUBDIVISIONS = 32
317		_LIGHT_DEFAULT_CAM_ID = 'default'
318		_LIGHT_DEFAULT_INTENSITY = 128
319		_LIGHT_DEFAULT_RADIUS = 10.0
320
321		print("Processing lights ... ")
322		lightelt = layerelt.find('lights')
323		if not lightelt:
324			print("\tno lights found on layer %s" % layer.getId())
325			return
326
327		lights = []
328		for attr in ('l', 'light', 'lgt'):
329			lights.extend(lightelt.findall(attr))
330
331		for light in lights:
332			group = light.get('group')
333			if not group:
334				print("Light has no group. Omitting...")
335				continue
336
337			blending_src = light.get('src')
338			if not blending_src:
339				blending_src = _LIGHT_DEFAULT_BLENDING_SRC
340			blending_dst = light.get('dst')
341			if not blending_dst:
342				blending_dst = _LIGHT_DEFAULT_BLENDING_DST
343
344			_x = light.get('x')
345			if not _x: _x = 0
346			_y = light.get('y')
347			if not _y: _y = 0
348			_z = light.get('y')
349			if not _z: _z = 0
350
351			node = {}
352			node['blending_src'] = int(blending_src)
353			node['blending_dst'] = int(blending_dst)
354			node['layer'] = layer.getId()
355			node['position'] = int(_x), int(_y), int(_z)
356
357			# where is the light? *sing*
358			instance_id = light.get('instance')
359			node['instance'] = None
360			if instance_id and layer.getInstance(instance_id):
361				node['instance'] = instance_id
362
363			type = light.get('type')
364			if type:
365				s_ref = light.get('s_ref')
366				if not s_ref: s_ref = -1
367				node['s_ref'] = int(s_ref)
368				a_ref = light.get('a_ref')
369				if not a_ref: a_ref = 0.0
370				node['a_ref'] = float(a_ref)
371
372				if type == 'image':
373					image = light.get('image')
374					if not image:
375						print("Light has no image. Omitting...")
376						continue
377					node['type'] = 'image'
378					image = reverse_root_subfile(self.source, image)
379					img = self.image_manager.create(image)
380					node['image'] = img
381				elif type == 'animation':
382					animation = light.get('animation')
383					if not animation:
384						print("Light has no animation. Omitting...")
385						continue
386					node['type'] = 'animation'
387					animation = reverse_root_subfile(self.source, animation)
388					anim = loadXMLAnimation(self.engine, animation)
389					node['animation'] = anim
390				elif type == 'simple':
391					node['type'] = type
392					radius = light.get('radius')
393					if not radius: radius = _LIGHT_DEFAULT_RADIUS
394					node['radius'] = float(radius)
395
396					subdivisions = light.get('subdivisions')
397					if not subdivisions:
398						subdivisions = _LIGHT_DEFAULT_SUBDIVISIONS
399					node['subdivisions'] = int(subdivisions)
400
401					intensity = light.get('intensity')
402					if not intensity:
403						intensity = _LIGHT_DEFAULT_INTENSITY
404					node['intensity'] = int(intensity)
405
406					xstretch = light.get('xstretch')
407					if not xstretch: xstretch = 1.0
408					ystretch = light.get('ystretch')
409					if not ystretch: ystretch = 1.0
410					node['stretch'] = float(xstretch), float(ystretch)
411
412					color = light.get('color')
413					if not color: color = '%d,%d,%d' % (255, 255, 255)
414					node['color'] = ([int(c) for c in color.split(',')])
415
416			else:
417				continue
418
419			cam_id = light.get('camera_id')
420			if not cam_id: cam_id = _LIGHT_DEFAULT_CAM_ID
421
422			if not cam_id in self.light_data:
423				self.light_data[cam_id] = {}
424			if group not in self.light_data[cam_id]:
425				self.light_data[cam_id][group] = []
426
427			self.light_data[cam_id][group].append(node)
428
429		for camera, groups in self.light_data.items():
430			print("Lights for camera %s" % camera)
431			for group, lights in groups.items():
432				print(group, lights)
433
434	def parse_sounds(self, layerelt, layer):
435		""" create sound emitter
436
437		FIXME:
438			- FIFE has a hard limit of sound emitters
439			  how should we load emitters here?
440			- my first thought: collect a list of sound
441			  files & data for emitter creation,
442			  then let the client decide what to do with it
443
444		@type	layerelt:	object
445		@param	layerelt:	ElementTree layer branch
446		@type	layer:	object
447		@param	layer:	FIFE layer object
448		"""
449		# to be continued
450		pass
451
452	def parse_instances(self, layerelt, layer):
453		""" create all layers and their instances
454
455		@type	layerelt:	object
456		@param	layerelt:	ElementTree layer branch
457		@type	layer:	object
458		@param	layer:	FIFE layer object
459		"""
460		instelt = layerelt.find('instances')
461
462		instances = []
463		for attr in ('i', 'inst', 'instance'):
464			instances.extend(instelt.findall(attr))
465
466		for instance in instances:
467			_id = instance.get('id')
468			if not _id:
469				_id = ''
470
471			objectID = ''
472			for attr in ('o', 'object', 'obj'):
473				objectID = instance.get(attr)
474				if objectID: break
475			if not objectID: self._err('<instance> %s does not specify an object attribute.' % str(objectID))
476			objectID = str(objectID)
477
478			nspace = ''
479			for attr in ('namespace', 'ns'):
480				nspace = instance.get(attr)
481				if nspace: break
482			# try to reuse the previous namespace
483			if not nspace and self.nspace:
484				nspace = self.nspace
485			if not nspace and not self.nspace: self._err('<instance> %s does not specify an object namespace, and no default is available.' % str(objectID))
486			nspace = str(nspace)
487			self.nspace = nspace
488
489			# check if there is an object for this instance available, if not -> skip this one
490			object = self.model.getObject(objectID, nspace)
491			if not object:
492				print("Object with id=%s, ns=%s could not be found. Omitting..." % (objectID, nspace))
493				continue
494
495			x = instance.get('x')
496			if x: self.x = x = float(x)
497			else: x = self.x
498
499			y = instance.get('y')
500			if y: self.y = y = float(y)
501			else: y = self.y
502
503			z = instance.get('z')
504			if z: z = float(z)
505			else: z = 0.0
506
507			inst = layer.createInstance(object, fife.ExactModelCoordinate(x,y,z), _id)
508
509			rotation = 0
510			for attr in ('r', 'rotation'):
511				rotation = instance.get(attr)
512				if rotation: break
513			if not rotation:
514				angles = object.get2dGfxVisual().getStaticImageAngles()
515				if angles:
516					rotation = angles[0]
517				else:
518					rotation = 0
519			else:
520				rotation = int(rotation)
521			inst.setRotation(rotation)
522
523			over_block = instance.get('override_blocking')
524			if over_block is not None:
525				inst.setOverrideBlocking(bool(over_block))
526				blocking = instance.get('blocking')
527				if blocking is not None:
528					inst.setBlocking(bool(int(blocking)))
529
530			fife.InstanceVisual.create(inst)
531
532			stackpos = instance.get('stackpos')
533			if stackpos:
534				inst.get2dGfxVisual().setStackPosition(int(stackpos))
535
536			if (object.getAction('default')):
537				target = fife.Location(layer)
538				inst.actRepeat('default', target)
539
540	def parse_cameras(self, mapelt, map):
541		""" create all cameras and activate them
542
543		FIXME:
544			- should the cameras really be enabled here?
545			  IMO that's part of the setup within a client
546			  (we just _load_ things here)
547
548		@type	mapelt:	object
549		@param	mapelt:	ElementTree root
550		@type	map:	object
551		@param	map:	FIFE map object
552		"""
553		if self.callback:
554			tmplist = mapelt.findall('camera')
555			i = float(0)
556
557		for camera in mapelt.findall('camera'):
558			_id = camera.get('id')
559			zoom = camera.get('zoom')
560			tilt = camera.get('tilt')
561			rotation = camera.get('rotation')
562			ref_cell_width = camera.get('ref_cell_width')
563			ref_cell_height = camera.get('ref_cell_height')
564			viewport = camera.get('viewport')
565			light_color = camera.get('light_color')
566
567			if not zoom: zoom = 1
568			if not tilt: tilt = 0
569			if not rotation: rotation = 0
570
571			if not _id: self._err('Camera declared without an id.')
572			if not (ref_cell_width and ref_cell_height): self._err(''.join(['Camera ', str(_id), ' declared without reference cell dimensions.']))
573
574			try:
575				if viewport:
576					cam = map.addCamera(str(_id), fife.Rect(*[int(c) for c in viewport.split(',')]))
577
578				else:
579					screen = self.engine.getRenderBackend()
580					cam = map.addCamera(str(_id), fife.Rect(0,0,screen.getScreenWidth(),screen.getScreenHeight()))
581
582				renderer = fife.InstanceRenderer.getInstance(cam)
583				renderer.activateAllLayers(map)
584
585			except fife.Exception as e:
586				print(e.getMessage())
587
588			if light_color: cam.setLightingColor(*[float(c) for c in light_color.split(',')])
589			cam.setCellImageDimensions(int(ref_cell_width), int(ref_cell_height))
590			cam.setRotation(float(rotation))
591			cam.setTilt(float(tilt))
592			cam.setZoom(float(zoom))
593
594			renderer = fife.InstanceRenderer.getInstance(cam)
595			renderer.activateAllLayers(map)
596
597			if self.callback:
598				i += 1
599				self.callback(self.msg['camera'] % str(_id), float( i / len(tmplist) * 0.25 + 0.75 ) )
600
601	def create_light_nodes(self, map):
602		""" loop through all preloaded lights and create them
603		according to their data
604
605		@type	map:	object
606		@param	map:	FIFE map object
607		"""
608		cameras = [i.getId() for i in map.getCameras()]
609		renderers = {}
610		default_cam = map.getCameras()[0].getId()
611
612		def add_simple_light(group, renderer, node, data):
613			""" add a node as simple light to the renderer
614
615			@type	group:	string
616			@param	group:	name of the light group
617			@type	renderer:	object
618			@param	renderer:	fife.LightRenderer instance
619			@type	node:		object
620			@param	node:		fife.RendererNode instance
621			@type	data:		dict
622			@param	data:		all data for the light type creation
623			"""
624			if not node: return
625			if not group: return
626			renderer.addSimpleLight(
627				group,
628				node,
629				data['intensity'],
630				data['radius'],
631				data['subdivisions'],
632				data['stretch'][0],
633				data['stretch'][1],
634				data['color'][0],
635				data['color'][1],
636				data['color'][2],
637				data['blending_src'],
638				data['blending_dst'],
639			)
640			if data['s_ref'] is not -1:
641				add_stencil_test(group, renderer, data)
642		def add_animated_lightmap(group, renderer, node, data):
643			""" add a node as animated lightmap to the renderer
644
645			@type	group:	string
646			@param	group:	name of the light group
647			@type	renderer:	object
648			@param	renderer:	fife.LightRenderer instance
649			@type	node:		object
650			@param	node:		fife.RendererNode instance
651			@type	data:		dict
652			@param	data:		all data for the light type creation
653			"""
654			if not node: return
655			if not group: return
656			renderer.addAnimation(
657				group,
658				node,
659				data['animation'],
660				data['blending_src'],
661				data['blending_dst'],
662			)
663			if data['s_ref'] is not -1:
664				add_stencil_test(group, renderer, data)
665		def add_lightmap(group, renderer, node, data):
666			""" add a node as lightmap to the renderer
667
668			@type	group:	string
669			@param	group:	name of the light group
670			@type	renderer:	object
671			@param	renderer:	fife.LightRenderer instance
672			@type	node:		object
673			@param	node:		fife.RendererNode instance
674			@type	data:		dict
675			@param	data:		all data for the light type creation
676			"""
677			if not node: return
678			if not group: return
679			renderer.addImage(
680				group,
681				node,
682				data['image'],
683				data['blending_src'],
684				data['blending_dst'],
685			)
686			if data['s_ref'] is not -1:
687				add_stencil_test(group, renderer, data)
688		def add_stencil_test(group, renderer, data):
689			""" add a stencil test to a group
690
691			@type	group:	string
692			@param	group:	name of the light group
693			@type	renderer:	object
694			@param	renderer:	fife.LightRenderer instance
695			@type	data:		dict
696			@param	data:		all data for the light type creation
697			"""
698			if not group: return
699			renderer.addStencilTest(
700				group,
701				data['s_ref'],
702				data['a_ref'],
703			)
704
705		def create_node(instance=None, point=None, layer=None):
706			""" creates a node of one of these types:
707
708				- attached to an instance
709				- attached to an instance with offset
710				- attached to a point
711
712				FIXME:
713					- add location node
714
715			@type:	instance:	object
716			@param	instance:	fife instance object
717			@type	point:		tuple
718			@param	point:		x,y,z tuple
719			@type	layer:		object
720			@param	layer:		fife layer object
721			"""
722			node = None
723			if not layer: return node
724
725			# node at layer coordinates
726			if point and not instance:
727				point = fife.Point(point[0], point[1])
728				node = fife.RendererNode(point);
729			# node with offset
730			if instance and point:
731				node = fife.RendererNode(instance, layer, fife.Point(point[0], point[1]))
732			# node attached to instance
733			if instance and not point:
734				node = fife.RendererNode(instance, layer)
735
736			if node:
737				node.thisown = 0
738
739			return node
740
741		def dump_data():
742			""" dump all loaded data """
743			for camera, groups in self.light_data.items():
744				print("Lights for camera %s" % camera)
745				for group, lights in groups.items():
746					print(group, lights)
747
748		# fetch all renderer instances for available cameras
749		for _id in cameras:
750			camera = map.getCamera(_id)
751			renderers[_id] = fife.LightRenderer.getInstance(camera)
752
753		# parse data and create the lights
754		for camera, groups in self.light_data.items():
755			for group, lights in groups.items():
756				for light in lights:
757					instance = None
758					layer = map.getLayer(light['layer'])
759					if light['instance']:
760						instance = layer.getInstance(light['instance'])
761					position = light['position']
762					node = create_node(instance, position, layer)
763
764					# some guards
765					if not node: continue
766
767					if camera == 'default':
768						renderer = renderers[default_cam]
769					else:
770						renderer = renderers[camera]
771
772					if light['type'] == 'simple':
773						add_simple_light(group, renderer, node, light)
774					elif light['type'] == 'image':
775						add_lightmap(group, renderer, node, light)
776					elif light['type'] == 'animation':
777						add_animated_lightmap(group, renderer, node, light)
778
779#		dump_data()
780
781
782