# -*- test-case-name: mv3d.test.test_scenery -*- # Copyright (C) 2008-2012 Mortal Coil Games # See LICENSE for details. # """ Scenery is basically something that can not move, but has a physical and visual representation. The top level item is the Scenery body, which contains multiple SceneryPieces. Each piece can have multiple colliders and visual objects allowing for a single scenery piece to be composited from several pieces. On the client side, the SceneryClient will have the paged geometry that the pieces will attach to. """ from twisted.internet.task import coiterate from mv3d.util.classgen import ClassGenerator from mv3d.util.persist import Integer, Persistable, FloatVector, List, Pickled, \ UntypedReference, Float, IDTuple from mv3d.net.pb import Cacheable, Copyable, observable from mv3d.net.security import Securable from mv3d.phys.body import BodyWithColliders, IVisualObject class SceneryPiece(Copyable, Securable, Persistable): """ An individual scenery piece """ position = FloatVector(autoSave=True, partialSave=True) rotation = FloatVector(autoSave=True, partialSave=True) assetID = IDTuple(autoSave=True, partialSave=True) def __init__(self): Securable.__init__(self) self.colliders = [] self.visualAssets = [] def addVisualAsset(self, aid, position, yRot=0.0, scale=1.0): """ Add a new visual asset """ self.visualAssets.append(dict(asset=aid, position=position, yRot=yRot, scale=scale)) class PieceIterator: """ Iterates through pieces and calls a func on them """ def __init__(self, pieces, func, *a, **kw): self.func = (func, a, kw) self.pieces = pieces.__iter__() def __iter__(self): return self def next(self): """ Implemnent the iter interface. Just get the next piece and call the func on it """ p = self.pieces.next() return self.func[0](p, *self.func[1], **self.func[2]) class Scenery(BodyWithColliders, Cacheable, Securable): """ Holds scenery """ _schemaVersion = 1 area = None space = None pageSize = Integer(default=50, autoSave=True, partialSave=True) batchPageLod = FloatVector(default=(100, 30), autoSave=True, partialSave=True) imposterPageLod = FloatVector(default=(4000, 50), autoSave=True, partialSave=True) pieces = List(UntypedReference()) def __init__(self): BodyWithColliders.__init__(self) Cacheable.__init__(self) Securable.__init__(self) self.pieces = [] self.grantPermission("read", "all") self.grantPermission("reference", "all") self.addExclude("area", "space") def create(self, world): """ We don't need to do anything """ def addPiece(self, piece): """ Add a scenery piece """ self.pieces.append(piece) addPiece, observe_addPiece = observable(addPiece) def enterSpace(self, space): """ Build the scenery in a space """ self.space = space return coiterate( PieceIterator(self.pieces, self.buildPiece)) def destroy(self): """ Destroy the scenery """ self.space = None return coiterate( PieceIterator(self.pieces, self.destroyPiece)) def enterArea(self, area): """ Enter an area """ self.area = area return self.enterSpace(area.getDisabledSpace()) def leaveArea(self, _area): """ Leave an area """ return self.destroy() def buildPiece(self, piece): """ Build a piece in our area """ for c in piece.colliders: c.build(space=self.space) def destroyPiece(self, piece): """ Destroy one piece """ for c in piece.colliders: c.destroy() def isEnabled(self): """ Always disabled """ return False def getPreferredSpaceType(self): """ Always disabled """ return 1 def getPosition(self): """ Return something... """ return (0, 0, 0) def getServerClassGenerators(self): """ Return class generators """ cgs = [] cgs.append(ClassGenerator().setFrom(self)) cgs.append(ClassGenerator().setFrom(SceneryPiece)) return cgs def getClientClassGenerators(self): """ Return class generators """ cgs = [] cgs.append(ClassGenerator(modulename="mv3d.client.view.visual", classname="SceneryView", sourceclass="mv3d.phys.scenery.Scenery")) cgs.append(ClassGenerator().setFrom(SceneryPiece)) return cgs class Grass(IVisualObject, Cacheable, Securable, Persistable): """ Defines a patch of grass (or flowers) """ stoppedObserving = Cacheable.stoppedObserving getStateToCacheAndObserveFor = Cacheable.getStateToCacheAndObserveFor setCopyableState = Cacheable.setCopyableState bounds = FloatVector(autoSave=True, partialSave=True, transmit=True) layers = List(Pickled(), transmit=True) heightField = UntypedReference(autoSave=True, partialSave=True, transmit=True) pageSize = Integer(autoSave=True, partialSave=True, transmit=True) maxDist = Float(autoSave=True, partialSave=True, transmit=True) def __init__(self, bounds=None, heightField=None, pageSize=100, maxDist=100): Persistable.__init__(self) Cacheable.__init__(self) Securable.__init__(self) self.layers = [] self.bounds = bounds self.heightField = heightField self.pageSize = pageSize self.maxDist = maxDist def create(self, _area, _pos, _rot): """ Do nothing on the server """ def addLayer(self, aid, bounds=None, animated=True, swaySpeed=1.5, swayLength=0.08, swayDistrib=10.0, density=0.01, colorMap=None, densityMap=None, minSize=(4.0, 2.0), maxSize=(8.0, 5.0), heightRange=None): """ Add a layer of grass """ self.layers.append(dict(aid=aid, bounds=bounds, animated=animated, swaySpeed=swaySpeed, swayLength=swayLength, swayDistrib=swayDistrib, density=density, colorMap=colorMap, densityMap=densityMap, minSize=minSize, maxSize=maxSize, heightRange=heightRange)) self.queueSave(selectAttributes=["layers"]) addLayer, observe_addLayer = observable(addLayer) def removeLayer(self, layer): """ Remove a layer of grass """ self.layers.pop(layer) self.queueSave(selectAttributes=["layers"]) removeLayer, observe_removeLayer = observable(removeLayer) def setAssetID(self, layer, assetID): """ Sets the assetID of the given layer """ self.layers[layer]["aid"] = assetID self.queueSave(selectAttributes=["layers"]) setAssetID, observe_setAssetID = observable(setAssetID) def setDensity(self, layer, density): """ Sets the density of the given layer """ self.layers[layer]["density"] = density self.queueSave(selectAttributes=["layers"]) setDensity, observe_setDensity = observable(setDensity) def setHeightRange(self, layer, heightRange): """ Sets the height level of the given layer """ self.layers[layer]["heightRange"] = heightRange self.queueSave(selectAttributes=["layers"]) setHeightRange, observe_setHeightRange = observable(setHeightRange) def setColorMap(self, layer, colorMap): """ Set the color map for a layer """ self.layers[layer]["colorMap"] = colorMap self.queueSave(selectAttributes=["layers"]) setColorMap, observe_setColorMap = observable(setColorMap) def setDensityMap(self, layer, densityMap): """ Set the density map for a layer """ self.layers[layer]["densityMap"] = densityMap self.queueSave(selectAttributes=["layers"]) setDensityMap, observe_setDensityMap = observable(setDensityMap) def setAnimation(self, layer, enabled): """ Turns animation off or on for a given layer """ self.layers[layer]["animated"] = enabled self.queueSave(selectAttributes=["layers"]) setAnimation, observe_setAnimation = observable(setAnimation) def setSwayLength(self, layer, length): """ Sets how far the grass sways in world units """ self.layers[layer]["swayLength"] = length self.queueSave(selectAttributes=["layers"]) setSwayLength, observe_setSwayLength = observable(setSwayLength) def setSwaySpeed(self, layer, speed): """ Sets how fast the grass sways """ self.layers[layer]["swaySpeed"] = speed self.queueSave(selectAttributes=["layers"]) setSwaySpeed, observe_setSwaySpeed = observable(setSwaySpeed) def setSwayDistrib(self, layer, amount): """ Sets how randomly the grass sways """ self.layers[layer]["swayDistrib"] = amount self.queueSave(selectAttributes=["layers"]) setSwayDistrib, observe_setSwayDistrib = observable(setSwayDistrib) def setSizeVariants(self, layer, minimum, maximum): """ Sets the minimum and maximum size in world units. Both args should be two tuples """ self.layers[layer]["minSize"] = minimum self.layers[layer]["maxSize"] = maximum self.queueSave(selectAttributes=["layers"]) setSizeVariants, observe_setSizeVariants = observable(setSizeVariants) def getServerClassGenerators(self): """ Return class generators """ cgs = self.heightField.getClassGenerators() cgs.append(ClassGenerator().setFrom(self)) return cgs def getClientClassGenerators(self): """ Return class generators """ cgs = [ClassGenerator(modulename="mv3d.client.view.visual", classname="GrassView", sourceclass="mv3d.phys.scenery.Grass")] if self.heightField is not None: cgs.extend(self.heightField.getClassGenerators()) return cgs