/*
 * Copyright 1999-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

package javax.media.j3d;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import javax.vecmath.Vector3d;

/**
 * A rendering environment structure is an object that organizes lights,
 * fogs, backgrounds, clips, and model clips.
 */

class RenderingEnvironmentStructure extends J3dStructure implements ObjectUpdate {
/**
 * The list of light nodes
 */
ArrayList<LightRetained> nonViewScopedLights = new ArrayList<LightRetained>();
HashMap<View, ArrayList<LightRetained>> viewScopedLights = new HashMap<View, ArrayList<LightRetained>>();
int numberOfLights = 0;

/**
 * The list of fog nodes
 */
ArrayList<FogRetained> nonViewScopedFogs = new ArrayList<FogRetained>();
HashMap<View, ArrayList<FogRetained>> viewScopedFogs = new HashMap<View, ArrayList<FogRetained>>();
int numberOfFogs = 0;

/**
 * The list of alternate app nodes
 */
ArrayList<AlternateAppearanceRetained> nonViewScopedAltAppearances = new ArrayList<AlternateAppearanceRetained>();
HashMap<View, ArrayList<AlternateAppearanceRetained>> viewScopedAltAppearances = new HashMap<View, ArrayList<AlternateAppearanceRetained>>();
int numberOfAltApps = 0;

/**
 * The list of model clip nodes
 */
ArrayList<ModelClipRetained> nonViewScopedModelClips = new ArrayList<ModelClipRetained>();
HashMap<View, ArrayList<ModelClipRetained>> viewScopedModelClips = new HashMap<View, ArrayList<ModelClipRetained>>();
int numberOfModelClips = 0;

/**
 * The list of background nodes
 */
ArrayList<BackgroundRetained> nonViewScopedBackgrounds = new ArrayList<BackgroundRetained>();
HashMap<View, ArrayList<BackgroundRetained>> viewScopedBackgrounds = new HashMap<View, ArrayList<BackgroundRetained>>();
int numberOfBgs = 0;

/**
 * The list of clip nodes
 */
ArrayList<ClipRetained> nonViewScopedClips = new ArrayList<ClipRetained>();
HashMap<View, ArrayList<ClipRetained>> viewScopedClips = new HashMap<View, ArrayList<ClipRetained>>();
int numberOfClips = 0;

    // For closest Background selection
    BackgroundRetained[] intersectedBacks = new BackgroundRetained[1];


    // For closest Clip selection
    ClipRetained[] intersectedClips = new ClipRetained[1];

    // For closest Background, Clip, Fog selection
    Bounds[] intersectedBounds = new Bounds[1];

    Transform3D localeXform = new Transform3D();
    Vector3d localeTranslation = new Vector3d();

    // For closest Fog selection
    FogRetained[] intersectedFogs = new FogRetained[1];

    // for closest alternate appearance selection
    AlternateAppearanceRetained[] intersectedAltApps = new AlternateAppearanceRetained[1];

    // For closest ModelClip selection
    ModelClipRetained[] intersectedModelClips = new ModelClipRetained[1];


    // Back clip distance in V world
    double backClipDistance;

// ArrayList of leafRetained object whose mirrorObjects
// should be updated
ArrayList<Object[]> objList = new ArrayList<Object[]>();

// ArrayList of leafRetained object whose boundingleaf xform
// should be updated
ArrayList<LeafRetained> xformChangeList = new ArrayList<LeafRetained>();

// freelist management of objects
private final ArrayList<Object[]> objFreeList = new ArrayList<Object[]>();

    LightRetained[] retlights = new LightRetained[5];

    // variables used for processing transform messages
    boolean transformMsg = false;
    UpdateTargets targets = null;
    ArrayList blUsers = null;

    Integer ogInsert = new Integer(J3dMessage.ORDERED_GROUP_INSERTED);
    Integer ogRemove = new Integer(J3dMessage.ORDERED_GROUP_REMOVED);

    // Used to lock the intersectedBounds {used by fog, mclip etc}
    // Can used intersectedBounds itself, since this may be realloced
    Object lockObj = new Object();

    /**
     * Constructs a RenderingEnvironmentStructure object in the specified
     * virtual universe.
     */
    RenderingEnvironmentStructure(VirtualUniverse u) {
	super(u, J3dThread.UPDATE_RENDERING_ENVIRONMENT);
    }


/**
 * Returns a object array of length 5 to save the 5 objects in the message list.
 */
Object[] getObjectArray() {
	int size = objFreeList.size();
	if (size == 0)
		return new Object[5];

	return objFreeList.remove(size - 1);
}

void addObjArrayToFreeList(Object[] objs) {
	Arrays.fill(objs, null);
	objFreeList.add(objs);
}

@Override
public void updateObject() {
	int size;

	size = objList.size();
	for (int i = 0; i < size; i++) {
		Object[] args = objList.get(i);
		LeafRetained leaf = (LeafRetained)args[0];
		leaf.updateMirrorObject(args);
		addObjArrayToFreeList(args);
	}
	objList.clear();

	size = xformChangeList.size();
	for (int i = 0; i < size; i++) {
		xformChangeList.get(i).updateTransformChange();
	}
	xformChangeList.clear();
}

    @Override
    void processMessages(long referenceTime) {
	J3dMessage[] messages = getMessages(referenceTime);;
	J3dMessage m;
	int nMsg = getNumMessage();

	if (nMsg <= 0) {
	    return;
	}

	for (int i=0; i < nMsg; i++) {
	    m = messages[i];

	    switch (m.type) {
	    case J3dMessage.INSERT_NODES:
		insertNodes(m);
		break;
	    case J3dMessage.REMOVE_NODES:
		removeNodes(m);
		break;
	    case J3dMessage.LIGHT_CHANGED:
		updateLight(m.args);
		break;
	    case J3dMessage.BOUNDINGLEAF_CHANGED:
		updateBoundingLeaf(m.args);
		break;
	    case J3dMessage.FOG_CHANGED:
		updateFog(m.args);
		break;
	    case J3dMessage.ALTERNATEAPPEARANCE_CHANGED:
		updateAltApp(m.args);
		break;
	    case J3dMessage.SHAPE3D_CHANGED:
		updateShape3D(m.args);
		break;
	    case J3dMessage.ORIENTEDSHAPE3D_CHANGED:
		updateOrientedShape3D(m.args);
		break;
	    case J3dMessage.MORPH_CHANGED:
		updateMorph(m.args);
		break;
	    case J3dMessage.TRANSFORM_CHANGED:
		transformMsg = true;
		break;
	    case J3dMessage.SWITCH_CHANGED:
		processSwitchChanged(m);
		// may need to process dirty switched-on transform
		if (universe.transformStructure.getLazyUpdate()) {
		    transformMsg = true;
		}
		break;
	    case J3dMessage.MODELCLIP_CHANGED:
		updateModelClip(m.args);
		break;
	    case J3dMessage.BACKGROUND_CHANGED:
		updateBackground(m.args);
		break;
	    case J3dMessage.CLIP_CHANGED:
		updateClip(m.args);
		break;
	    case J3dMessage.ORDERED_GROUP_INSERTED:
		updateOrderedGroupInserted(m);
		break;
	    case J3dMessage.ORDERED_GROUP_REMOVED:
		updateOrderedGroupsRemoved(m);
		break;
	    case J3dMessage.VIEWSPECIFICGROUP_CHANGED:
		updateViewSpecificGroupChanged(m);
		break;
	    case J3dMessage.VIEWSPECIFICGROUP_INIT:
		initViewSpecificInfo(m);
		break;
	    case J3dMessage.VIEWSPECIFICGROUP_CLEAR:
		clearViewSpecificInfo(m);
		break;
	    }
	    m.decRefcount();
	}

	if (transformMsg) {
	    updateTransformChange();
	    transformMsg = false;
	}

	VirtualUniverse.mc.addMirrorObject(this);

	Arrays.fill(messages, 0, nMsg, null);
    }

    void updateOrderedGroupInserted(J3dMessage m) {
	Object[] ogList = (Object[])m.args[0];
	Object[] ogChildIdList = (Object[])m.args[1];
	Object[] ogOrderedIdList = (Object[])m.args[2];
	OrderedGroupRetained og;

	for (int n = 0; n < ogList.length; n++) {
	    og = (OrderedGroupRetained)ogList[n];
	    og.updateChildIdTableInserted(((Integer) ogChildIdList[n]).intValue(),
					  ((Integer) ogOrderedIdList[n]).intValue());
	    og.incrChildCount();
	}
    }

    void updateOrderedGroupsRemoved(J3dMessage m) {
	Object[] ogList = (Object[])m.args[0];
	Object[] ogChildIdList = (Object[])m.args[1];
	OrderedGroupRetained og;

	for (int n = 0; n < ogList.length; n++) {
	    og = (OrderedGroupRetained)ogList[n];
	    og.updateChildIdTableRemoved(((Integer) ogChildIdList[n]).intValue());
	    og.decrChildCount();
	}

    }
    /**
     * This processes a switch change.
     */
    void processSwitchChanged(J3dMessage m) {
        int i;
        UnorderList arrList;
        int size;
        Object[] nodes, nodesArr;
        LeafRetained leaf;

        UpdateTargets targets = (UpdateTargets)m.args[0];

        arrList = targets.targetList[Targets.BLN_TARGETS];
        if (arrList != null) {
            BoundingLeafRetained mbleaf;
            Object[] objArr = (Object[])m.args[1];
            Object[] obj;
            size = arrList.size();
            nodesArr = arrList.toArray(false);

            for (int h=0; h<size; h++) {
                nodes = (Object[])nodesArr[h];
                obj = (Object[])objArr[h];

                for (i=0; i<nodes.length; i++) {

                    Object[] users = (Object[])obj[i];
                    mbleaf = (BoundingLeafRetained)nodes[i];

                    //mbleaf.switchState.updateCurrentSwitchOn();
                    for (int j = 0; j < users.length; j++) {
                        leaf = (LeafRetained)users[j];
                        if (leaf instanceof FogRetained ||
                            leaf instanceof LightRetained ||
                            leaf instanceof ModelClipRetained ||
                            leaf instanceof ClipRetained ||
                            leaf instanceof AlternateAppearanceRetained ||
                            leaf instanceof BackgroundRetained) {
                            leaf.updateBoundingLeaf();
                        }
                    }
                }
            }
        }
    }

    void insertNodes(J3dMessage m) {
	Object[] nodes = (Object[])m.args[0];
	ArrayList<NodeRetained> viewScopedNodes = (ArrayList<NodeRetained>)m.args[3];
	ArrayList<ArrayList<View>> scopedNodesViewList = (ArrayList<ArrayList<View>>)m.args[4];
	int i;

	for (i=0; i<nodes.length; i++) {
	    Object n = nodes[i];
	    if (n instanceof LightRetained) {
         LightRetained lt = (LightRetained)n;
		numberOfLights++;

		// If this particulat light is not scoped, added it
		// to all the views
		if (lt.inBackgroundGroup)
		    lt.geometryBackground.lights.add(lt);
		else
		    nonViewScopedLights.add(lt);

	    } else if (n instanceof FogRetained) {
                FogRetained fg = (FogRetained)n;
		numberOfFogs++;
		// If the fog is scoped to a view , then ..

		if (fg.inBackgroundGroup) {
		    fg.geometryBackground.fogs.add(fg);
		} else {
		    nonViewScopedFogs.add(fg);
		}


	    } else if (n instanceof AlternateAppearanceRetained) {
		AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)n;

		numberOfAltApps++;

		nonViewScopedAltAppearances.add(altApp);

	    } else if (n instanceof BackgroundRetained) {
		BackgroundRetained bg = (BackgroundRetained)n;
		numberOfBgs++;

		nonViewScopedBackgrounds.add(bg);

	    } else if (n instanceof ClipRetained) {
		ClipRetained cl = (ClipRetained)n;
		numberOfClips++;

		nonViewScopedClips.add(cl);

	    } else if (n instanceof ModelClipRetained) {
		ModelClipRetained mc = (ModelClipRetained)n;
		numberOfModelClips++;

		nonViewScopedModelClips.add(mc);

	    }

	}


	if (viewScopedNodes != null) {
	    int size = viewScopedNodes.size();
	    int vlsize;


	    for (i = 0; i < size; i++) {
		NodeRetained n = viewScopedNodes.get(i);
		ArrayList<View> vl = scopedNodesViewList.get(i);
			if (n instanceof LightRetained) {
				LightRetained lt = (LightRetained) n;
				lt.isViewScoped = true;
				numberOfLights++;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<LightRetained> list = viewScopedLights.get(view);
					if (list == null) {
						list = new ArrayList<LightRetained>();
						viewScopedLights.put(view, list);
					}
					list.add(lt);
				}
			}
			else if (n instanceof FogRetained) {
				FogRetained ft = (FogRetained) n;
				ft.isViewScoped = true;
				numberOfFogs++;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<FogRetained> list = viewScopedFogs.get(view);
					if (list == null) {
						list = new ArrayList<FogRetained>();
						viewScopedFogs.put(view, list);
					}
					list.add(ft);
				}
			}
			else if (n instanceof AlternateAppearanceRetained) {
				AlternateAppearanceRetained aart = (AlternateAppearanceRetained) n;
				aart.isViewScoped = true;
				numberOfAltApps++;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<AlternateAppearanceRetained> list = viewScopedAltAppearances
							.get(view);
					if (list == null) {
						list = new ArrayList<AlternateAppearanceRetained>();
						viewScopedAltAppearances.put(view, list);
					}
					list.add(aart);
				}
			}
			else if (n instanceof BackgroundRetained) {
				BackgroundRetained bt = (BackgroundRetained) n;
				bt.isViewScoped = true;
				numberOfBgs++;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<BackgroundRetained> list = viewScopedBackgrounds
							.get(view);
					if (list == null) {
						list = new ArrayList<BackgroundRetained>();
						viewScopedBackgrounds.put(view, list);
					}
					list.add(bt);
				}
			}
			else if (n instanceof ClipRetained) {
				ClipRetained ct = (ClipRetained) n;
				ct.isViewScoped = true;
				numberOfClips++;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<ClipRetained> list = viewScopedClips.get(view);
					if (list == null) {
						list = new ArrayList<ClipRetained>();
						viewScopedClips.put(view, list);
					}
					list.add(ct);
				}
		} else if (n instanceof ModelClipRetained) {
			ModelClipRetained mt = (ModelClipRetained)n;
		    mt.isViewScoped = true;
		    numberOfModelClips++;
		    vlsize = vl.size();
		    for (int k = 0; k < vlsize; k++) {
			View view = vl.get(k);
			ArrayList<ModelClipRetained> list = viewScopedModelClips.get(view);
			if (list == null) {
			    list = new ArrayList<ModelClipRetained>();
			    viewScopedModelClips.put(view, list);
			}
			list.add(mt);
		    }
		}
	    }

	}
	if (numberOfLights > retlights.length)
	    retlights = new LightRetained[numberOfLights];
	if (intersectedFogs.length < numberOfFogs)
	    intersectedFogs = new FogRetained[numberOfFogs];
	if (intersectedAltApps.length < numberOfAltApps)
	    intersectedAltApps = new AlternateAppearanceRetained[numberOfAltApps];
	if (intersectedBacks.length < numberOfBgs)
	    intersectedBacks = new BackgroundRetained[numberOfBgs];
	if (intersectedClips.length < numberOfClips)
	    intersectedClips = new ClipRetained[numberOfClips];
	if (intersectedModelClips.length < numberOfModelClips)
	    intersectedModelClips = new ModelClipRetained[numberOfModelClips];
    }

    @Override
    void removeNodes(J3dMessage m) {
	Object[] nodes = (Object[])m.args[0];
	ArrayList<NodeRetained> viewScopedNodes = (ArrayList<NodeRetained>)m.args[3];
	ArrayList<ArrayList<View>> scopedNodesViewList = (ArrayList<ArrayList<View>>)m.args[4];
        int i;
	GeometryAtom ga;
	LeafRetained oldsrc = null;

	// System.err.println("RE : removeNodes message " + m);
	// System.err.println("RE : removeNodes m.args[0] " + m.args[0]);

	for (i=0; i<nodes.length; i++) {
	    Object n = nodes[i];
	    if (n instanceof LightRetained) {
		LightRetained lt = (LightRetained)n;
		if (lt.inBackgroundGroup) {
		    lt.geometryBackground.lights.remove(lt);
		}
		else {
		    nonViewScopedLights.remove(lt);
		}

		numberOfLights--;
	    } else if (n instanceof FogRetained) {
		numberOfFogs--;
                FogRetained fg = (FogRetained)n;
		if (fg.inBackgroundGroup) {
		    fg.geometryBackground.fogs.remove(fg);
		} else {
		    nonViewScopedFogs.remove(nonViewScopedFogs.indexOf(n));
		}
	    } else if (n instanceof  AlternateAppearanceRetained) {
		numberOfAltApps--;
	        nonViewScopedAltAppearances.remove(nonViewScopedAltAppearances.indexOf(n));
	    }else if (n instanceof BackgroundRetained) {
		numberOfBgs--;
	        nonViewScopedBackgrounds.remove(nonViewScopedBackgrounds.indexOf(n));
	    } else if (n instanceof ClipRetained) {
		numberOfClips--;
	        nonViewScopedClips.remove(nonViewScopedClips.indexOf(n));
	    } else if (n instanceof ModelClipRetained) {
		ModelClipRetained mc = (ModelClipRetained)n;
		numberOfModelClips--;
		nonViewScopedModelClips.remove(mc);

	    }
	    else if (n instanceof GeometryAtom) {
		ga = (GeometryAtom)n;
		// Check that we have not already cleared the mirrorobject
		// since mant geometry atoms could be generated for one
		// mirror shape
		if (ga.source != oldsrc) {
		    ga.source.clearMirrorShape();
		    oldsrc = ga.source;
		}
	    }
	    else if (n instanceof OrderedGroupRetained) {
		// Clear the orderedBins for this orderedGroup
		((OrderedGroupRetained)n).clearDerivedDataStructures();
	    }
	}
	if (viewScopedNodes != null) {
	    int size = viewScopedNodes.size();
	    int vlsize;
	    for (i = 0; i < size; i++) {
		NodeRetained n = viewScopedNodes.get(i);
		ArrayList<View> vl = scopedNodesViewList.get(i);
			if (n instanceof LightRetained) {
				LightRetained lt = (LightRetained) n;
				lt.isViewScoped = false;
				numberOfLights--;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<LightRetained> list = viewScopedLights.get(view);
					list.remove(lt);
					if (list.size() == 0) {
						viewScopedLights.remove(view);
					}
				}
			}
			else if (n instanceof FogRetained) {
				FogRetained ft = (FogRetained)n;
				ft.isViewScoped = false;
				numberOfFogs--;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<FogRetained> list = viewScopedFogs.get(view);
					list.remove(ft);
					if (list.size() == 0) {
						viewScopedFogs.remove(view);
					}
				}
			} else if (n instanceof AlternateAppearanceRetained) {
				AlternateAppearanceRetained aart = (AlternateAppearanceRetained) n;
				aart.isViewScoped = false;
				numberOfAltApps--;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<AlternateAppearanceRetained> list = viewScopedAltAppearances
							.get(view);
					list.remove(aart);
					if (list.size() == 0) {
						viewScopedAltAppearances.remove(view);
					}
				}
			}
			else if (n instanceof BackgroundRetained) {
				BackgroundRetained bt = (BackgroundRetained)n;
				bt.isViewScoped = false;
				numberOfBgs--;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<BackgroundRetained> list = viewScopedBackgrounds.get(view);
					list.remove(bt);
					if (list.size() == 0) {
						viewScopedBackgrounds.remove(view);
					}
				}
			}
			else if (n instanceof ClipRetained) {
				ClipRetained ct = (ClipRetained) n;
				ct.isViewScoped = false;
				numberOfClips--;
				vlsize = vl.size();
				for (int k = 0; k < vlsize; k++) {
					View view = vl.get(k);
					ArrayList<ClipRetained> list = viewScopedClips.get(view);
					list.remove(ct);
					if (list.size() == 0) {
						viewScopedClips.remove(view);
					}
				}
		} else if (n instanceof ModelClipRetained) {
			ModelClipRetained mt = (ModelClipRetained)n;
		    mt.isViewScoped = false;
		    numberOfModelClips--;
		    vlsize = vl.size();
		    for (int k = 0; k < vlsize; k++) {
			View view = vl.get(k);
			ArrayList<ModelClipRetained> list = viewScopedModelClips.get(view);
			list.remove(mt);
			if (list.size() == 0) {
			    viewScopedModelClips.remove(view);
			}
		    }
		}
	    }

	}
    }

    LightRetained[] getInfluencingLights(RenderAtom ra, View view) {
	LightRetained[] lightAry = null;
	int i, j;

	// Need to lock retlights, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized (retlights) {
		ArrayList<LightRetained> globalLights;
		int numLights = 0;
		if (ra.geometryAtom.source.inBackgroundGroup) {
			globalLights = ra.geometryAtom.source.geometryBackground.lights;
			numLights = processLights(globalLights, ra, numLights);
		}
		else {
			if ((globalLights = viewScopedLights.get(view)) != null) {
				numLights = processLights(globalLights, ra, numLights);
			}
			// now process the common lights
			numLights = processLights(nonViewScopedLights, ra, numLights);
		}

	    boolean newLights = false;
	    if (ra.lights != null && ra.lights.length == numLights) {
		for (i=0; i<ra.lights.length; i++) {
		    for (j=0; j<numLights; j++) {
			if (ra.lights[i] == retlights[j]) {
			    break;
			}
		    }
		    if (j==numLights) {
			newLights = true;
			break;
		    }
		}
	    } else {
		newLights = true;
	    }
	    if (newLights) {
		lightAry = new LightRetained[numLights];
		for (i = 0; i < numLights; i++) {
		    lightAry[i] = retlights[i];
		}
		return (lightAry);
	    } else {
		return(ra.lights);
	    }
	}
    }

// Called while holding the retlights lock
private int processLights(ArrayList<LightRetained> globalLights, RenderAtom ra, int numLights) {
	LightRetained[] shapeScopedLt;
        Bounds bounds;
	int i, j, n;
        bounds = ra.localeVwcBounds;
	int size = globalLights.size();

	if (size > 0) {
	    for (i=0; i<size; i++) {
			LightRetained light = globalLights.get(i);
		//		System.err.println("vwcBounds = "+bounds);
		//		System.err.println("light.region = "+light.region);
		//		System.err.println("Intersected = "+bounds.intersect(light.region));
		//		System.err.println("");

		//		    if ((light.viewList != null && light.viewList.contains(view)) &&
		// Treat lights in background geo as having infinite bounds
		if (light.lightOn && light.switchState.currentSwitchOn &&
		    (ra.geometryAtom.source.inBackgroundGroup || bounds.intersect(light.region))){
		    // Get the mirror Shape3D node
		    n = ra.geometryAtom.source.numlights;
		    shapeScopedLt = ra.geometryAtom.source.lights;

		    // System.err.println("numLights per shape= "+n);
		    // scoped Fog/light is kept in the original
		    // shape3D node, what happens if this list changes
		    // while accessing them?. So, Lock.
		    if (light.isScoped) {
			for (j = 0; j < n; j++) {
			    // then check if the light is scoped to
			    // this group
			    if (light == shapeScopedLt[j]) {
				retlights[numLights++] = light;
				break;
			    }
			}
		    }
		    else {
			retlights[numLights++] = light;
		    }
		}
	    }
	}
	return numLights;

    }

FogRetained getInfluencingFog(RenderAtom ra, View view) {
	FogRetained fog = null;
	int j, nfogs;
	Bounds closestBounds;

	// Need to lock lockObj, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized(lockObj) {
	    nfogs = 0;
	    Bounds bounds = ra.localeVwcBounds;

	    if (intersectedBounds.length < numberOfFogs)
		intersectedBounds = new Bounds[numberOfFogs];

	    ArrayList<FogRetained> globalFogs;
		if (ra.geometryAtom.source.inBackgroundGroup) {
			globalFogs = ra.geometryAtom.source.geometryBackground.fogs;
			nfogs = processFogs(globalFogs, ra, nfogs);
			// If background, then nfogs > 1, take the first one
			if (nfogs >= 1)
				fog = intersectedFogs[0];

		}
		else {
			if ((globalFogs = viewScopedFogs.get(view)) != null) {
				nfogs = processFogs(globalFogs, ra, nfogs);
			}
		// now process the common fogs
		nfogs = processFogs(nonViewScopedFogs, ra, nfogs);


		if (nfogs == 1)
		    fog = intersectedFogs[0];

		else if (nfogs > 1) {
		    closestBounds = bounds.closestIntersection(intersectedBounds);
		    for (j= 0; j < nfogs; j++) {
			if (intersectedBounds[j] == closestBounds) {
			    fog = intersectedFogs[j];
			    break;
			}
		    }
		}
	    }
	    return (fog);
	}
    }

    // Called while holding lockObj lock
    int processFogs(ArrayList<FogRetained> globalFogs, RenderAtom ra, int numFogs) {
	int size = globalFogs.size();
	FogRetained fog;
	int i, k, n;
        Bounds bounds = ra.localeVwcBounds;
	FogRetained[] shapeScopedFog;

	if (globalFogs.size() > 0) {
	    for (i = 0 ; i < size; i++) {
		fog = globalFogs.get(i);
		// Note : There is no enable check for fog
		if (fog.region != null && fog.switchState.currentSwitchOn &&
		    (ra.geometryAtom.source.inBackgroundGroup || fog.region.intersect(bounds))) {
		    n = ra.geometryAtom.source.numfogs;
		    shapeScopedFog = ra.geometryAtom.source.fogs;

		    if (fog.isScoped) {
			for (k = 0; k < n; k++) {
			    // then check if the Fog is scoped to
			    // this group
			    if (fog == shapeScopedFog[k]) {
				intersectedBounds[numFogs] = fog.region;
				intersectedFogs[numFogs++] = fog;
				break;
			    }
			}
		    }
		    else {
			intersectedBounds[numFogs] = fog.region;
			intersectedFogs[numFogs++] = fog;
		    }
		}
	    }
	}
	return numFogs;
    }

ModelClipRetained getInfluencingModelClip(RenderAtom ra, View view) {
	if (ra.geometryAtom.source.inBackgroundGroup)
		return null;

	// Need to lock lockObj, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized (lockObj) {
		Bounds bounds = ra.localeVwcBounds;
		int nModelClips = 0;
		if (intersectedBounds.length < numberOfModelClips)
			intersectedBounds = new Bounds[numberOfModelClips];

		ArrayList<ModelClipRetained> globalModelClips = viewScopedModelClips.get(view);
		if (globalModelClips  != null)
			nModelClips = processModelClips(globalModelClips, ra, nModelClips);

		// now process the common clips
		nModelClips = processModelClips(nonViewScopedModelClips, ra, nModelClips);

		ModelClipRetained modelClip = null;
		if (nModelClips == 1)
			modelClip = intersectedModelClips[0];
		else if (nModelClips > 1) {
			Bounds closestBounds = bounds.closestIntersection(intersectedBounds);
			for (int j = 0; j < nModelClips; j++) {
				if (intersectedBounds[j] == closestBounds) {
					modelClip = intersectedModelClips[j];
					break;
				}
			}
		}
		return modelClip;
	}
}

int processModelClips(ArrayList<ModelClipRetained> globalModelClips, RenderAtom ra, int nModelClips) {
    	int size = globalModelClips.size();
	int i, k, n;
	ModelClipRetained modelClip;
        Bounds bounds = ra.localeVwcBounds;
	ModelClipRetained[] shapeScopedModelClip;

	if (size > 0) {
	    for (i = 0; i < size; i++) {
		modelClip = globalModelClips.get(i);
		if (modelClip.enableFlag == true &&
		    modelClip.region != null && modelClip.switchState.currentSwitchOn) {
		    if (modelClip.region.intersect(bounds) == true) {
			n = ra.geometryAtom.source.numModelClips;
			shapeScopedModelClip = ra.geometryAtom.source.modelClips;

			if (modelClip.isScoped) {
			    for (k = 0; k < n; k++) {
				// then check if the modelClip is scoped to
				// this group
				if (shapeScopedModelClip[k] == modelClip) {

				    intersectedBounds[nModelClips] = modelClip.region;
				    intersectedModelClips[nModelClips++] = modelClip;
				    break;
				}
			    }
			}
			else {
			    intersectedBounds[nModelClips] = modelClip.region;
			    intersectedModelClips[nModelClips++] = modelClip;
			}
		    }
		}
	    }
	}
	return nModelClips;
    }

    BackgroundRetained getApplicationBackground(BoundingSphere bounds, Locale viewLocale, View view) {
	BackgroundRetained bg = null;
	Bounds closestBounds;
	int j = 0;
	int nbacks;

	// Need to lock lockObj, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized(lockObj) {
	    nbacks = 0;
	    if (intersectedBounds.length < numberOfBgs)
		intersectedBounds = new Bounds[numberOfBgs];


		ArrayList<BackgroundRetained> globalBgs = viewScopedBackgrounds.get(view);
		if (globalBgs != null)
			nbacks = processBgs(globalBgs, bounds, nbacks, viewLocale);

		nbacks = processBgs(nonViewScopedBackgrounds, bounds, nbacks, viewLocale);

	    // If there are no intersections, set to black.
	    if (nbacks == 1) {
		bg = intersectedBacks[0];
	    } else  if (nbacks > 1) {
		closestBounds =
		    bounds.closestIntersection(intersectedBounds);
		for (j=0; j<nbacks; j++) {
		    if (intersectedBounds[j] == closestBounds) {
			bg = intersectedBacks[j];
			//System.err.println("matched " + closestBounds);
			break;
		    }
		}
	    }
	    return (bg);
	}
    }


// Called while holding lockObj lock
int processBgs(ArrayList<BackgroundRetained> globalBgs, BoundingSphere bounds, int nbacks, Locale viewLocale) {
	int size = globalBgs.size();

	for (int i = 0; i < size; i++) {
		BackgroundRetained back = globalBgs.get(i);
		if (back.transformedRegion == null || !back.switchState.currentSwitchOn)
			continue;

		if (back.cachedLocale != viewLocale) {
			Bounds localeBounds = (Bounds)back.transformedRegion.clone();
			// Translate the transformed region
			back.cachedLocale.hiRes.difference(viewLocale.hiRes, localeTranslation);
			localeXform.setIdentity();
			localeXform.setTranslation(localeTranslation);
			localeBounds.transform(localeXform);
			if (localeBounds.intersect(bounds) == true) {
				intersectedBounds[nbacks] = localeBounds;
				intersectedBacks[nbacks++] = back;
			}
		}
		else {
			if (back.transformedRegion.intersect(bounds) == true) {
				intersectedBounds[nbacks] = back.transformedRegion;
				intersectedBacks[nbacks++] = back;
			}
		}
	}
	return nbacks;
}

    double[] backClipDistanceInVworld (BoundingSphere bounds, View view) {
	int j;
        Bounds closestBounds;
	boolean backClipActive;
	double[] backClipDistance;
	double distance;

	// Need to lock intersectedBounds, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized(lockObj) {
	    backClipDistance = null;
	    backClipActive = false;
		int nclips = 0;
	    distance = 0.0;
	    if (intersectedBounds.length < numberOfClips)
		intersectedBounds = new Bounds[numberOfClips];

		ArrayList<ClipRetained> globalClips = viewScopedClips.get(view);
		if (globalClips != null)
			nclips = processClips(globalClips, bounds, nclips);

		nclips = processClips(nonViewScopedClips, bounds, nclips);


	    if (nclips == 1)  {
		distance = intersectedClips[0].backDistanceInVworld;
		backClipActive = true;
	    } else if (nclips > 1) {
		closestBounds =
		    bounds.closestIntersection(intersectedBounds);
		for (j=0; j < nclips; j++) {
		    if (intersectedBounds[j] == closestBounds) {
			distance = intersectedClips[j].backDistanceInVworld;
			backClipActive = true;
			break;
		    }
		}
	    }
	    if (backClipActive) {
		backClipDistance = new double[1];
		backClipDistance[0] = distance;
	    }
	    return (backClipDistance);
	}
    }

int processClips(ArrayList<ClipRetained> globalClips, BoundingSphere bounds, int nclips) {
	int size = globalClips.size();

	for (int i = 0; i < size; i++) {
		ClipRetained clip = globalClips.get(i);
		if (clip.transformedRegion != null &&
		    clip.transformedRegion.intersect(bounds) == true &&
		    clip.switchState.currentSwitchOn) {
			intersectedBounds[nclips] = clip.transformedRegion;
			intersectedClips[nclips++] = clip;
		}
	}
	return nclips;
}


    void updateLight(Object[] args) {
	Object[] objs;
	LightRetained light = (LightRetained)args[0];
	int component = ((Integer)args[1]).intValue();
	// Store the value to be updated during object update
	// If its an ambient light, then if color changed, update immediately
	if ((component & (LightRetained.INIT_MIRROR)) != 0) {
	    light.initMirrorObject(args);
	}

	if (light instanceof AmbientLightRetained &&
	    ((component & LightRetained.COLOR_CHANGED) != 0)) {
	    light.updateImmediateMirrorObject(args);
	}
	else if ((component & (LightRetained.COLOR_CHANGED|
			       LightRetained.INIT_MIRROR |
			  PointLightRetained.POSITION_CHANGED |
			  PointLightRetained.ATTENUATION_CHANGED|
			  DirectionalLightRetained.DIRECTION_CHANGED |
			  SpotLightRetained.DIRECTION_CHANGED |
			  SpotLightRetained.CONCENTRATION_CHANGED |
			  SpotLightRetained.ANGLE_CHANGED)) != 0) {
	    objs = getObjectArray();
	    objs[0] = args[0];
	    objs[1] = args[1];
	    objs[2] = args[2];
	    objs[3] = args[3];
	    objs[4] = args[4];

	    objList.add(objs);
	}
	else if ((component & LightRetained.CLEAR_MIRROR) != 0) {
	    light.clearMirrorObject(args);
	}
	else  {
	    light.updateImmediateMirrorObject(args);
	}



    }

    void updateBackground(Object[] args) {
	BackgroundRetained bg = (BackgroundRetained)args[0];
	bg.updateImmediateMirrorObject(args);
    }

    void updateFog(Object[] args) {
	Object[] objs;
	FogRetained fog = (FogRetained)args[0];
	int component = ((Integer)args[1]).intValue();
	if ((component & FogRetained.INIT_MIRROR) != 0) {
	    fog.initMirrorObject(args);
	    // Color, distance et all should be updated when renderer
	    // is not running ..
	    objs = getObjectArray();
	    objs[0] = args[0];
	    objs[1] = args[1];
	    objs[2] = args[2];
	    objs[3] = args[3];
	    objs[4] = args[4];
	    objList.add(objs);
	}
	else if ((component & FogRetained.CLEAR_MIRROR) != 0) {
	    fog.clearMirrorObject(args);
	// Store the value to be updated during object update
	} else if ((component & (FogRetained.COLOR_CHANGED |
			  LinearFogRetained.FRONT_DISTANCE_CHANGED|
			  LinearFogRetained.BACK_DISTANCE_CHANGED |
			  ExponentialFogRetained.DENSITY_CHANGED)) != 0) {
	    objs = getObjectArray();
	    objs[0] = args[0];
	    objs[1] = args[1];
	    objs[2] = args[2];
	    objs[3] = args[3];
	    objs[4] = args[4];
	    objList.add(objs);
	}
	else {
	    fog.updateImmediateMirrorObject(args);
	}


    }

    void updateAltApp(Object[] args) {
	AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)args[0];
	int component = ((Integer)args[1]).intValue();
	if ((component & AlternateAppearanceRetained.INIT_MIRROR) != 0) {
	    AlternateAppearanceRetained altapp = (AlternateAppearanceRetained)args[0];
	    altapp.initMirrorObject(args);
	}
	else if ((component & AlternateAppearanceRetained.CLEAR_MIRROR) != 0) {
	    AlternateAppearanceRetained altapp = (AlternateAppearanceRetained)args[0];
	    altapp.clearMirrorObject(args);
	}
	else {
	    altApp.updateImmediateMirrorObject(args);
	}


    }

    void updateClip(Object[] args) {
	ClipRetained clip = (ClipRetained)args[0];
	clip.updateImmediateMirrorObject(args);
    }

    void updateModelClip(Object[] args) {
	ModelClipRetained modelClip = (ModelClipRetained)args[0];
	Object[] objs;
	int component = ((Integer)args[1]).intValue();

	if ((component & ModelClipRetained.INIT_MIRROR) != 0) {
	    modelClip.initMirrorObject(args);
	}
	if ((component & ModelClipRetained.CLEAR_MIRROR) != 0) {
	    modelClip.clearMirrorObject(args);
	}
	else if ((component & (ModelClipRetained.PLANES_CHANGED |
			  ModelClipRetained.INIT_MIRROR |
			  ModelClipRetained.PLANE_CHANGED)) != 0) {
	    objs = getObjectArray();
	    objs[0] = args[0];
	    objs[1] = args[1];
	    objs[2] = args[2];
	    objs[3] = args[3];
	    objs[4] = args[4];
	    objList.add(objs);
	}
	else {
	    modelClip.updateImmediateMirrorObject(args);
	}

    }

    void updateBoundingLeaf(Object[] args) {
	BoundingLeafRetained bl = (BoundingLeafRetained)args[0];
	Object[] users = (Object[])(args[3]);
	bl.updateImmediateMirrorObject(args);
	// Now update all users of this bounding leaf object
	for (int i = 0; i < users.length; i++) {
	    LeafRetained mLeaf = (LeafRetained)users[i];
	    mLeaf.updateBoundingLeaf();
	}
    }

    void updateShape3D(Object[] args) {
	Shape3DRetained shape = (Shape3DRetained)args[0];
	shape.updateImmediateMirrorObject(args);
    }

    void updateOrientedShape3D(Object[] args) {
	OrientedShape3DRetained shape = (OrientedShape3DRetained)args[4];
	shape.updateImmediateMirrorObject(args);
    }

    void updateMorph(Object[] args) {
	MorphRetained morph = (MorphRetained)args[0];
	morph.updateImmediateMirrorObject(args);
    }


    void updateTransformChange() {
	int i,j;
 	Object[] nodes, nodesArr;
	BoundingLeafRetained bl;
        UnorderList arrList;
        int size;

	targets = universe.transformStructure.getTargetList();
	blUsers = universe.transformStructure.getBlUsers();

        // process misc environment nodes
        arrList = targets.targetList[Targets.ENV_TARGETS];
        if (arrList != null) {
            size = arrList.size();
            nodesArr = arrList.toArray(false);

            for (j = 0; j < size; j++) {
                nodes = (Object[])nodesArr[j];

                for (i = 0; i < nodes.length; i++) {
	    	    if (nodes[i] instanceof LightRetained) {
			LightRetained ml = (LightRetained)nodes[i];
			ml.updateImmediateTransformChange();
			xformChangeList.add(ml);

	    	    } else if (nodes[i] instanceof FogRetained) {
			FogRetained mfog = (FogRetained) nodes[i];
			mfog.updateImmediateTransformChange();
			xformChangeList.add(mfog);

	    	    } else if (nodes[i] instanceof AlternateAppearanceRetained){
			AlternateAppearanceRetained mAltApp =
					(AlternateAppearanceRetained) nodes[i];
			mAltApp.updateImmediateTransformChange();
			xformChangeList.add(mAltApp);

	    	    } else if (nodes[i] instanceof BackgroundRetained) {
                	BackgroundRetained bg = (BackgroundRetained) nodes[i];
                	bg.updateImmediateTransformChange();

            	    } else if (nodes[i] instanceof ModelClipRetained) {
			ModelClipRetained mc = (ModelClipRetained) nodes[i];
			mc.updateImmediateTransformChange();
                    }
                }
            }
        }

        // process BoundingLeaf nodes
        arrList = targets.targetList[Targets.BLN_TARGETS];
        if (arrList != null) {
            size = arrList.size();
            nodesArr = arrList.toArray(false);
            for (j = 0; j < size; j++) {
                nodes = (Object[])nodesArr[j];
                for (i = 0; i < nodes.length; i++) {
                    bl = (BoundingLeafRetained)nodes[i];
                    bl.updateImmediateTransformChange();
                }
            }
        }

	// Now notify the list of all users of bounding leaves
	// to update its boundingleaf transformed region
	if (blUsers != null) {
	    for (i = 0; i < blUsers.size(); i++) {
		LeafRetained leaf = (LeafRetained) blUsers.get(i);
		leaf.updateBoundingLeaf();
	    }
	}
	targets = null;
	blUsers = null;
    }


    // The first element is TRUE, if alternate app is in effect
    // The second element return the appearance that should be used
    // Note , I can't just return null for app, then I don't know
    // if the appearance is null or if the alternate app in not
    // in effect
    Object[]  getInfluencingAppearance(RenderAtom ra, View view) {
	int j;
	Bounds closestBounds;
        Bounds bounds;
	Object[] retVal;
	retVal = new Object[2];

	if (ra.geometryAtom.source.inBackgroundGroup) {
	    retVal[0] = Boolean.FALSE;
	    return retVal;
	}

	// Need to lock lockObj, since on a multi-processor
	// system with 2 views on a single universe, there might
	// be councurrent access
	synchronized(lockObj) {
	    int nAltApp = 0;
	    bounds = ra.localeVwcBounds;

	    if (intersectedBounds.length < numberOfAltApps)
		intersectedBounds = new Bounds[numberOfAltApps];

		ArrayList<AlternateAppearanceRetained> globalAltApps = viewScopedAltAppearances.get(view);
		if (globalAltApps != null)
			nAltApp = processAltApps(globalAltApps, ra, nAltApp);

		nAltApp = processAltApps(nonViewScopedAltAppearances, ra, nAltApp);
		AlternateAppearanceRetained altApp = null;
	    if (nAltApp == 1)
		altApp = intersectedAltApps[0];
	    else if (nAltApp > 1) {
		closestBounds = bounds.closestIntersection(intersectedBounds);
		for (j= 0; j < nAltApp; j++) {
		    if (intersectedBounds[j] == closestBounds) {
			altApp = intersectedAltApps[j];
			break;
		    }
		}
	    }
	    if (altApp == null) {
		retVal[0] = Boolean.FALSE;
		return retVal;
	    } else {
		retVal[0] = Boolean.TRUE;
		retVal[1] = altApp.appearance;
		return retVal;
	    }
	}
    }

// Called while holding lockObj lock
int processAltApps(ArrayList<AlternateAppearanceRetained> globalAltApps, RenderAtom ra, int nAltApp) {
	int size = globalAltApps.size();
	Bounds bounds = ra.localeVwcBounds;
	AlternateAppearanceRetained[] shapeScopedAltApp;

	if (size == 0)
		return nAltApp;

	for (int i = 0; i < size; i++) {
		AlternateAppearanceRetained altApp = globalAltApps.get(i);
		// System.err.println("altApp.region = "+altApp.region+" altApp.switchState.currentSwitchOn = "+altApp.switchState.currentSwitchOn+" intersect = "+altApp.region.intersect(ra.geometryAtom.vwcBounds));
		// System.err.println("altApp.isScoped = "+altApp.isScoped);
		// Note : There is no enable check for fog
		if (altApp.region == null || !altApp.switchState.currentSwitchOn)
			continue;

		if (altApp.region.intersect(bounds) == true) {
			int n = ra.geometryAtom.source.numAltApps;
			shapeScopedAltApp = ra.geometryAtom.source.altApps;
			if (altApp.isScoped) {
				for (int k = 0; k < n; k++) {
					// then check if the light is scoped to
					// this group
					if (altApp == shapeScopedAltApp[k]) {

						intersectedBounds[nAltApp] = altApp.region;
						intersectedAltApps[nAltApp++] = altApp;
						break;
					}
				}
			}
			else {
				intersectedBounds[nAltApp] = altApp.region;
				intersectedAltApps[nAltApp++] = altApp;
			}
		}
	}

	return nAltApp;
}

    void initViewSpecificInfo(J3dMessage m) {
	int[] keys = (int[])m.args[2];
	ArrayList<ArrayList<View>> vlists = (ArrayList<ArrayList<View>>)m.args[1];
	ArrayList<ViewSpecificGroupRetained> vsgs = (ArrayList<ViewSpecificGroupRetained>)m.args[0];
	if (vsgs != null) {
	    //	    System.err.println("===> non null Vsg");
	    int size = vsgs.size();
	    for (int i = 0; i < size; i++) {
		ViewSpecificGroupRetained v = vsgs.get(i);
		ArrayList<View> l = vlists.get(i);
		int index = keys[i];
		//		System.err.println("v = "+v+" index = "+index+" l = "+l);
		v.cachedViewList.add(index, l);
		/*
		for (int k = 0; k <  v.cachedViewList.size(); k++) {
		    System.err.println("v = "+v+" k = "+k+" v.cachedViewList.get(k) = "+v.cachedViewList.get(k));
		}
		*/
	    }
	}
    }

    void clearViewSpecificInfo(J3dMessage m) {
	int[] keys = (int[])m.args[1];
	ArrayList<ViewSpecificGroupRetained> vsgs = (ArrayList<ViewSpecificGroupRetained>)m.args[0];
	if (vsgs != null) {
	    int size = vsgs.size();
	    for (int i = 0; i < size; i++) {
		ViewSpecificGroupRetained v = vsgs.get(i);
		int index = keys[i];
			if (index == -1) {
				int csize = v.cachedViewList.size();
				for (int j = 0; j < csize; j++) {
					v.cachedViewList.get(j).clear();
				}
				v.cachedViewList.clear();
			}
			else {
				v.cachedViewList.remove(index).clear();
			}
	    }
	}
    }

    void updateViewSpecificGroupChanged(J3dMessage m) {
	int component = ((Integer)m.args[0]).intValue();
	Object[] objAry = (Object[])m.args[1];

	ArrayList<LightRetained> ltList = null;
	ArrayList<FogRetained> fogList = null;
	ArrayList<ModelClipRetained> mclipList = null;
	ArrayList<AlternateAppearanceRetained> altAppList = null;
	ArrayList<BackgroundRetained> bgList = null;
	ArrayList<ClipRetained> clipList = null;

	if (((component & ViewSpecificGroupRetained.ADD_VIEW) != 0) ||
	    ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) {
	    int i;
	    Object obj;
	    View view = (View)objAry[0];
	    ArrayList  vsgList = (ArrayList)objAry[1];
	    ArrayList leafList = (ArrayList)objAry[2];
	    int[] keyList = (int[])objAry[3];
	    int size = vsgList.size();
	    // Don't add null views

	    if (view != null) {
		for (i = 0; i < size; i++) {
		    ViewSpecificGroupRetained vsg = (ViewSpecificGroupRetained)vsgList.get(i);
		    int index = keyList[i];
		    vsg.updateCachedInformation(ViewSpecificGroupRetained.ADD_VIEW, view, index);

		}
		size = leafList.size();
		// Leaves is non-null only for the top VSG

		if (size > 0) {
		    // Now process the list of affected leaved
		    for( i = 0; i < size; i++) {
			obj = leafList.get(i);
			if (obj instanceof LightRetained) {
						LightRetained lt = (LightRetained)obj;
						lt.isViewScoped = true;
						numberOfLights++;
						if (ltList == null) {
							if ((ltList = viewScopedLights.get(view)) == null) {
								ltList = new ArrayList<LightRetained>();
								viewScopedLights.put(view, ltList);
							}
						}
						ltList.add(lt);
			}
			if (obj instanceof FogRetained) {
						FogRetained ft = (FogRetained)obj;
						ft.isViewScoped = true;
						numberOfFogs++;
						if (fogList == null) {
							if ((fogList = viewScopedFogs.get(view)) == null) {
								fogList = new ArrayList<FogRetained>();
								viewScopedFogs.put(view, fogList);
							}
						}
						fogList.add(ft);
			}
			if (obj instanceof ModelClipRetained) {
						ModelClipRetained mc = (ModelClipRetained)obj;
						mc.isViewScoped = true;
						numberOfModelClips++;
						if (mclipList == null) {
							if ((mclipList = viewScopedModelClips.get(view)) == null) {
								mclipList = new ArrayList<ModelClipRetained>();
								viewScopedModelClips.put(view, mclipList);
							}
						}
						mclipList.add(mc);
			}
			if (obj instanceof AlternateAppearanceRetained) {
						AlternateAppearanceRetained aart = (AlternateAppearanceRetained)obj;
						aart.isViewScoped = true;
						numberOfAltApps++;
						if (altAppList == null) {
							if ((altAppList = viewScopedAltAppearances
									.get(view)) == null) {
								altAppList = new ArrayList<AlternateAppearanceRetained>();
								viewScopedAltAppearances.put(view, altAppList);
							}
						}
						altAppList.add(aart);
			}
			if (obj instanceof ClipRetained) {
						ClipRetained ct = (ClipRetained) obj;
						ct.isViewScoped = true;
						numberOfClips++;
						if (clipList == null) {
							if ((clipList = viewScopedClips.get(view)) == null) {
								clipList = new ArrayList<ClipRetained>();
								viewScopedClips.put(view, clipList);
							}
						}
						clipList.add(ct);
			}
			if (obj instanceof BackgroundRetained) {
						BackgroundRetained bg = (BackgroundRetained) obj;
						bg.isViewScoped = true;
						numberOfBgs++;
						if (bgList == null) {
							if ((bgList = viewScopedBackgrounds.get(view)) == null) {
								bgList = new ArrayList<BackgroundRetained>();
								viewScopedBackgrounds.put(view, bgList);
							}
						}
						bgList.add(bg);
					}
		    }
		    if (numberOfLights > retlights.length)
			retlights = new LightRetained[numberOfLights];
		    if (intersectedFogs.length < numberOfFogs)
			intersectedFogs = new FogRetained[numberOfFogs];
		    if (intersectedAltApps.length < numberOfAltApps)
			intersectedAltApps = new AlternateAppearanceRetained[numberOfAltApps];
		    if (intersectedBacks.length < numberOfBgs)
			intersectedBacks = new BackgroundRetained[numberOfBgs];
		    if (intersectedClips.length < numberOfClips)
			intersectedClips = new ClipRetained[numberOfClips];
		    if (intersectedModelClips.length < numberOfModelClips)
			intersectedModelClips = new ModelClipRetained[numberOfModelClips];
		}
	    }
	}
	if (((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0)||
	    ((component & ViewSpecificGroupRetained.SET_VIEW) != 0)) {
	    int i;
	    Object obj;
	    ArrayList  vsgList;
	    ArrayList leafList;
	    int[] keyList;
	    View view;

	    if ((component & ViewSpecificGroupRetained.REMOVE_VIEW) != 0) {
		view = (View)objAry[0];
		vsgList = (ArrayList)objAry[1];
		leafList = (ArrayList)objAry[2];
		keyList = (int[])objAry[3];
	    }
	    else {
		view = (View)objAry[4];
		vsgList = (ArrayList)objAry[5];
		leafList = (ArrayList)objAry[6];
		keyList = (int[])objAry[7];
	    }
	    // Don't add null views
	    if (view != null) {
		int size = vsgList.size();
		for (i = 0; i < size; i++) {
		    ViewSpecificGroupRetained vsg = (ViewSpecificGroupRetained)vsgList.get(i);
		    int index = keyList[i];
		    vsg.updateCachedInformation(ViewSpecificGroupRetained.REMOVE_VIEW, view, index);

		}
		size = leafList.size();
		// Leaves is non-null only for the top VSG
		if (size > 0) {
		    // Now process the list of affected leaved
		    for( i = 0; i < size; i++) {
			obj =  leafList.get(i);
			if (obj instanceof LightRetained) {
			    ((LightRetained)obj).isViewScoped = false;
			    numberOfLights--;
			    if (ltList == null) {
				ltList = viewScopedLights.get(view);
			    }
			    ltList.remove(obj);
			}
			if (obj instanceof FogRetained) {
			    ((FogRetained)obj).isViewScoped = false;
			    numberOfFogs--;
			    if (fogList == null) {
				fogList = viewScopedFogs.get(view);
			    }
			    fogList.remove(obj);
			}
			if (obj instanceof ModelClipRetained) {
			    ((ModelClipRetained)obj).isViewScoped = false;
			    numberOfModelClips--;
			    if (mclipList == null) {
				mclipList = viewScopedModelClips.get(view);
			    }
			    mclipList.remove(obj);
			}
			if (obj instanceof AlternateAppearanceRetained) {
			    ((AlternateAppearanceRetained)obj).isViewScoped = false;
			    numberOfAltApps--;
			    if (altAppList == null) {
				altAppList = viewScopedAltAppearances.get(view);
			    }
			    altAppList.remove(obj);
			}
			if (obj instanceof ClipRetained) {
			    ((ClipRetained)obj).isViewScoped = false;
			    numberOfClips--;
			    if (clipList == null) {
				clipList = viewScopedClips.get(view);
			    }
			    clipList.remove(obj);
			}
			if (obj instanceof BackgroundRetained) {
			    ((BackgroundRetained)obj).isViewScoped = false;
			    numberOfBgs++;
			    if (bgList == null) {
				bgList = viewScopedBackgrounds.get(view);
			    }
			    bgList.remove(obj);
			}
		    }

		    // If there are no more lights scoped to the view,
		    // remove the mapping
		    if (ltList != null && ltList.size() == 0)
			viewScopedLights.remove(view);
		    if (fogList != null && fogList.size() == 0)
			viewScopedFogs.remove(view);
		    if (mclipList != null && mclipList.size() == 0)
			viewScopedModelClips.remove(view);
		    if (altAppList != null && altAppList.size() == 0)
			viewScopedAltAppearances.remove(view);
		    if (clipList != null && clipList.size() == 0)
			viewScopedClips.remove(view);
		    if (bgList != null && bgList.size() == 0)
			viewScopedBackgrounds.remove(view);
		}
	    }
	}

    }

boolean isLightScopedToThisView(Object obj, View view) {
	LightRetained light = (LightRetained)obj;
	if (light.isViewScoped) {
		ArrayList<LightRetained> l = viewScopedLights.get(view);
		// If this is a scoped lights, but has no views then ..
		if (l == null || !l.contains(light))
			return false;
	}
	return true;
}

boolean isFogScopedToThisView(Object obj, View view) {
	FogRetained fog = (FogRetained)obj;
	if (fog.isViewScoped) {
		ArrayList<FogRetained> l = viewScopedFogs.get(view);
		// If this is a scoped fog, but has no views then ..
		if (l == null || !l.contains(fog))
			return false;
	}
	return true;
}

boolean isAltAppScopedToThisView(Object obj, View view) {
	AlternateAppearanceRetained altApp = (AlternateAppearanceRetained)obj;
	if (altApp.isViewScoped) {
		ArrayList<AlternateAppearanceRetained> l = viewScopedAltAppearances.get(view);
		// If this is a scoped altapp, but has no views then ..
		if (l == null || !l.contains(altApp))
			return false;
	}
	return true;
}

boolean isBgScopedToThisView(Object obj, View view) {
	BackgroundRetained bg = (BackgroundRetained)obj;
	if (bg.isViewScoped) {
		ArrayList<BackgroundRetained> l = viewScopedBackgrounds.get(view);
		// If this is a scoped bg, but has no views then ..
		if (l == null || !l.contains(bg))
			return false;
	}
	return true;
}

boolean isClipScopedToThisView(Object obj, View view) {
	ClipRetained clip = (ClipRetained)obj;
	if (clip.isViewScoped) {
		ArrayList<ClipRetained> l = viewScopedClips.get(view);
		// If this is a scoped clip, but has no views then ..
		if (l == null || !l.contains(clip))
			return false;
	}
	return true;
}


boolean isMclipScopedToThisView(Object obj, View view) {
	ModelClipRetained mclip = (ModelClipRetained)obj;
	if (mclip.isViewScoped) {
		ArrayList<ModelClipRetained> l = viewScopedModelClips.get(view);
		// If this is a scoped mclip, but has no views then ..
		if (l == null || !l.contains(mclip))
			return false;
	}
	return true;
}

@Override
void cleanup() {}
}