In the course of making contraptions in Garry's Mod, you notice very quickly that the heavier something is, the stronger the connections it has to other things, especially with ballsockets and welds. The trouble then is that, when you're making an intricate contraption, having very heavy objects involved can throw off the balance and mechanics inherent in it.
Therefore, we need a way to have a heavy object not act like a heavy object, we need to counteract the mass. To do this, we need to oppose the gravity that the Source engine places on it based on its mass, and for that we need the tick rate of the server.
Unfortunately, there is no expression 2 function presently that returns the tick rate, but there is a very simple calculation we can do to find out. ApplyForce codes need to run per tick to be fully functional, so we have the tick running on a tick-by-tick basis anyway. We can also retrieve the time that has passed on the server with curtime(). This means we can tell how much time has passed between ticks by checking the time in two side-by-side tick-speed operations. The difference then is X, and if we have 1/X we get the number of ticks that occurs within one second on the server, the tick rate. From there, it's a simple matter of gravity()/(1/X) to get the amount of force per unit of mass in an entity that is being exerted by gravity on it, Y.
And then, simply, applyForce upwards by vec(0,0,Mass*Y)*Z, Z being the percentage of the entity's mass that we want to counteract.
And now the code:
@persist S AG E:entity
if(first()){
runOnTick(1) #Run chip every tick
S=curtime() #Start time
E=entity():isWeldedTo()
}
elseif(!AG){
T=curtime() #End time
#Separation (Time of 1 tick)
Dif=T-S
#Number of ticks in 1 second
Tick=1/Dif
AG=gravity()/Tick
}
else{
Comp=1
Vec=vec(0,0,E:mass()*AG)*Comp
E:applyForce(Vec)
}
Friday, 9 April 2010
Monday, 15 February 2010
Alarei Telekinetic Applications Glove (ATAG)
This is something I've been putting together today for a larger project (Predominantly because it's dang near impossible to make hands on the proper scale out of actual props, but holos have no physics).
You can change whether it's a lefty or a righty, by changing the LR input between 1 and -1. Initial tests say it shouldn't matter how or where you spawn the chip, the holo should remain stable. The fingers are nonfunctional at the moment but the hand should still point it's palm ominously towards the input Entity.
After that, you use the State input to determine the mode of the glove, state 1 holds objects and moves them around, whereas state 2 allows you to rotate them (Naturally I recommend two gloves for that reason). There's no player user interface so I'm afraid you're on your own for that right now.
And now, onto the code:
@name Alarei Telekinetic Applications Glove
@inputs State Entity:entity LR Posi:vector
@inputs LockA Ang:angle
@inputs LockP Dif:vector Vel
@persist [Pos Sca Angle Mod Par]:array H Mat:string Start
@persist An:angle
@persist [V Col]:vector A:angle Core:entity
##MANUAL##
#Input 1 into LR for a Right hand, -1 for a Left hand
#State 1 is positioning, state 2 is angling
#In either state, the glove only affects the input Entity
#Positioning
#Flip LockP to freeze the entity relative to the glove
#Input a Dif to move it in a given direction on grabbing
#Vel adds the Dif each tick for sustained velocity
#Angling
#Flip LockA to freeze the entity's rotational movement
#Input Ang to rotate the entity to a desired angle
#LockA overrides Ang if both are input
if(first()){
Pos:pushVector(vec()) Sca:pushVector(vec(0.5,0.417,0.333)) Angle:pushAngle(ang()) Mod:pushString("hqtorus2") Par:pushNumber(1)
Pos:pushVector(vec()) Sca:pushVector(vec(0.417,0.417,0.10)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(0,2*LR,-0.5)) Sca:pushVector(vec(0.249,0.249,0.124)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(0,0,-0.143)) Sca:pushVector(vec(0.457,0.457,0.124)) Angle:pushAngle(ang(180,0,0)) Mod:pushString("dome2") Par:pushNumber(1)
Pos:pushVector(vec(0,3*LR,-0.5)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(4)
Pos:pushVector(vec(1,3*LR,-0.5)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(5)
Pos:pushVector(vec(2,-2*LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(3,-2*LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(7)
Pos:pushVector(vec(5,-2*LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(8)
Pos:pushVector(vec(6,-2*LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(9)
Pos:pushVector(vec(3,LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(4,LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(11)
Pos:pushVector(vec(6,LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(12)
Pos:pushVector(vec(7,LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(13)
H=1
Mat="models/combine_scanner/scanner_eye"
}
if(Pos:count()){interval(1000)}
else{runOnTick(1)}
if(!Start){
N=0
while(N<=10 & Pos:count()){
P=entity():toWorld(vec(0,0,30))+Pos[1,vector]
S=Sca[1,vector]
A=Angle[1,angle]
M=Mod[1,string]
Pa=Par[1,number]
holoCreate(H,P,S,A)
holoModel(H,M)
holoParent(H,Pa)
H++,N++
Pos:remove(1)
Sca:remove(1)
Angle:remove(1)
Mod:remove(1)
Par:remove(1)
if(!Pos:count()){H=1}
}
GM=holoEntity(14):getMaterial()
while(!Pos:count() & GM!=Mat & N<=10){
holoMaterial(H,Mat)
H++,N++
}
if(!Pos:count() & GM==Mat){
Start=1
Core=holoEntity(2)
}
}
if(LR & $LR){reset()}
if(Start){
if(Posi){holoPos(1,Posi)}
else{holoPos(1,entity():toWorld(vec(0,0,30)))}
holoMaterial(2,(State ? "models/props_combine/tpballglow" : ""))
Co=Core:getColor()
if(Co!=Col){
Core:soundPlay(1,1,"buttons/button9.wav")
Col=Co
}
Box=Entity:toWorld(Entity:boxCenter())
if(Entity & State){
An=(Box-holoEntity(1):pos()):toAngle()
holoAng(1,A:setRoll(0)-ang(22.5,0,0))
}
#Relative locking mechanism
LP=LockP & $LockP & State==1
#Positional locking
if(LP){
V=Core:toLocal(Box)
V+=Dif
}
elseif(!LockP){
V=Core:toLocal(Box)
}
#Angular locking
if(LockA & Entity & $LockA|!LockA){
A=Entity:angles()
}
Mass=Entity:mass() VelW=Entity:vel()
if(State==1){ #Positioning
holoColor(2,vec(0,1,0)*255)
V+=Dif*Vel #Application of applied velocity
Des=Core:toWorld(V)
Vec=Des-Box
Vec*=Mass
Vec-=VelW/2
Entity:applyForce(Vec*Mass)
}
elseif(State==2){ #Angling
holoColor(2,vec(1,0,1)*255)
Ine=Entity:inertia() AVel=Entity:angVelVector()
Ag=(LockA ? A : Ang)
Q1=quat(Entity)
Q2=quat(Ag)
Q=Q2/Q1
Torq=Entity:toLocal(rotationVector(Q)+Box)*Mass^2
Torq-=AVel*Mass
Torq*Ine
Entity:applyTorque(Torq)
}
else{
holoColor(2,vec(0,1,1)*255)
}
}
You can change whether it's a lefty or a righty, by changing the LR input between 1 and -1. Initial tests say it shouldn't matter how or where you spawn the chip, the holo should remain stable. The fingers are nonfunctional at the moment but the hand should still point it's palm ominously towards the input Entity.
After that, you use the State input to determine the mode of the glove, state 1 holds objects and moves them around, whereas state 2 allows you to rotate them (Naturally I recommend two gloves for that reason). There's no player user interface so I'm afraid you're on your own for that right now.
And now, onto the code:
@name Alarei Telekinetic Applications Glove
@inputs State Entity:entity LR Posi:vector
@inputs LockA Ang:angle
@inputs LockP Dif:vector Vel
@persist [Pos Sca Angle Mod Par]:array H Mat:string Start
@persist An:angle
@persist [V Col]:vector A:angle Core:entity
##MANUAL##
#Input 1 into LR for a Right hand, -1 for a Left hand
#State 1 is positioning, state 2 is angling
#In either state, the glove only affects the input Entity
#Positioning
#Flip LockP to freeze the entity relative to the glove
#Input a Dif to move it in a given direction on grabbing
#Vel adds the Dif each tick for sustained velocity
#Angling
#Flip LockA to freeze the entity's rotational movement
#Input Ang to rotate the entity to a desired angle
#LockA overrides Ang if both are input
if(first()){
Pos:pushVector(vec()) Sca:pushVector(vec(0.5,0.417,0.333)) Angle:pushAngle(ang()) Mod:pushString("hqtorus2") Par:pushNumber(1)
Pos:pushVector(vec()) Sca:pushVector(vec(0.417,0.417,0.10)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(0,2*LR,-0.5)) Sca:pushVector(vec(0.249,0.249,0.124)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(0,0,-0.143)) Sca:pushVector(vec(0.457,0.457,0.124)) Angle:pushAngle(ang(180,0,0)) Mod:pushString("dome2") Par:pushNumber(1)
Pos:pushVector(vec(0,3*LR,-0.5)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(4)
Pos:pushVector(vec(1,3*LR,-0.5)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(5)
Pos:pushVector(vec(2,-2*LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(3,-2*LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(7)
Pos:pushVector(vec(5,-2*LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(8)
Pos:pushVector(vec(6,-2*LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(9)
Pos:pushVector(vec(3,LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(1)
Pos:pushVector(vec(4,LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(11)
Pos:pushVector(vec(6,LR,0)) Sca:pushVector(vec(0.083,0.083,0.083)) Angle:pushAngle(ang()) Mod:pushString("hqicosphere2") Par:pushNumber(12)
Pos:pushVector(vec(7,LR,0)) Sca:pushVector(vec(0.167,0.167,0.333)) Angle:pushAngle(ang(90,0,0)) Mod:pushString("hqcylinder") Par:pushNumber(13)
H=1
Mat="models/combine_scanner/scanner_eye"
}
if(Pos:count()){interval(1000)}
else{runOnTick(1)}
if(!Start){
N=0
while(N<=10 & Pos:count()){
P=entity():toWorld(vec(0,0,30))+Pos[1,vector]
S=Sca[1,vector]
A=Angle[1,angle]
M=Mod[1,string]
Pa=Par[1,number]
holoCreate(H,P,S,A)
holoModel(H,M)
holoParent(H,Pa)
H++,N++
Pos:remove(1)
Sca:remove(1)
Angle:remove(1)
Mod:remove(1)
Par:remove(1)
if(!Pos:count()){H=1}
}
GM=holoEntity(14):getMaterial()
while(!Pos:count() & GM!=Mat & N<=10){
holoMaterial(H,Mat)
H++,N++
}
if(!Pos:count() & GM==Mat){
Start=1
Core=holoEntity(2)
}
}
if(LR & $LR){reset()}
if(Start){
if(Posi){holoPos(1,Posi)}
else{holoPos(1,entity():toWorld(vec(0,0,30)))}
holoMaterial(2,(State ? "models/props_combine/tpballglow" : ""))
Co=Core:getColor()
if(Co!=Col){
Core:soundPlay(1,1,"buttons/button9.wav")
Col=Co
}
Box=Entity:toWorld(Entity:boxCenter())
if(Entity & State){
An=(Box-holoEntity(1):pos()):toAngle()
holoAng(1,A:setRoll(0)-ang(22.5,0,0))
}
#Relative locking mechanism
LP=LockP & $LockP & State==1
#Positional locking
if(LP){
V=Core:toLocal(Box)
V+=Dif
}
elseif(!LockP){
V=Core:toLocal(Box)
}
#Angular locking
if(LockA & Entity & $LockA|!LockA){
A=Entity:angles()
}
Mass=Entity:mass() VelW=Entity:vel()
if(State==1){ #Positioning
holoColor(2,vec(0,1,0)*255)
V+=Dif*Vel #Application of applied velocity
Des=Core:toWorld(V)
Vec=Des-Box
Vec*=Mass
Vec-=VelW/2
Entity:applyForce(Vec*Mass)
}
elseif(State==2){ #Angling
holoColor(2,vec(1,0,1)*255)
Ine=Entity:inertia() AVel=Entity:angVelVector()
Ag=(LockA ? A : Ang)
Q1=quat(Entity)
Q2=quat(Ag)
Q=Q2/Q1
Torq=Entity:toLocal(rotationVector(Q)+Box)*Mass^2
Torq-=AVel*Mass
Torq*Ine
Entity:applyTorque(Torq)
}
else{
holoColor(2,vec(0,1,1)*255)
}
}
Saturday, 6 February 2010
Eye code testing system (ECTS)
So with my work recently on making different ranger-based eyes, I've been in need of a more in-depth way of discerning the quality of their results, which isn't really possible if all I'm seeing is a bunch of holos of various colours in a circle or somesuch in midair. So with that in mind, I decided to try and translate to a player-viewable experience what the eye was actually seeing.
After quite a bit of frustration and a quick translation from wires to wirelinks where possible, here we have the Eye Code Testing System (ECTS for short).
The eye itself can output the arrays or there can be an intermediary chip translating the ranger outputs to arrays, so long as the eye views based on an angle and a world position (Or can at least be readily modified to do so) it can be tested by the player with this system. Simply wirelink a cam controller and adv. pod controller to the E2 and wire up in the array inputs. The system can test variable focusing, entity-detection, as well as distance, hit, and colour recognition.
And now, the code:
After quite a bit of frustration and a quick translation from wires to wirelinks where possible, here we have the Eye Code Testing System (ECTS for short).
The eye itself can output the arrays or there can be an intermediary chip translating the ranger outputs to arrays, so long as the eye views based on an angle and a world position (Or can at least be readily modified to do so) it can be tested by the player with this system. Simply wirelink a cam controller and adv. pod controller to the E2 and wire up in the array inputs. The system can test variable focusing, entity-detection, as well as distance, hit, and colour recognition.
And now, the code:
@name Eye Code Testing System (ECTS)
@inputs [Posit Hit HNorm Ent]:array
@inputs [Cam Pod]:wirelink
@outputs Pos:vector Ang:angle Range Foc
@persist [RHit Colour Distance Entities Norm Rel] Speed
@persist Box:entity Max [In Po]:vector On
@persist [PoA HiA HNA EnA]:array
@persist T M B Inc
@trigger none
if(first()){
#====Functional Variables====#
Range=200
Speed=5
Distance=1 #Can the eye distinguish distances?
RHit=1 #Can the eye tell if it's hit something?
Colour=0 #Can the eye discern colour?
Entities=1 #Can the eye discern entities?
#Colour & Entities override one another
Norm=1 #Can the eye discern the hitnormal?
Rel=1 #Does the eye produce local vectors?
#============================#
#=======Function Start=======#
#============================#
T=Posit:count() #It helps to wire up Posit & refresh here
B=T+1 #Box hologram number
M=1
Foc=Range/4 #Default focus
Inc=Range/100 #Focus shift increment
Po=vec(0,0,2000) #Location of Box hologram
Pos=rangerOffset(600,vec(),vec(0,0,-1)):position()+vec(0,0,64)
In=Pos #Default start position for the eye
}
if(M<=B & !tickClk()){
interval(1000)
N=0
while(N<=10 & M<=B){
holoCreate(M,Po)
holoModel(M,(Norm ? "hqcone" : "hqicosphere2"))
if(M==B){
D=Range*2.5
holoScaleUnits(B,vec(1,1,1)*-D)
holoModel(B,"hqicosphere2")
holoColor(B,vec())
Box=holoEntity(B)
Max=200
}
else{
holoScale(M,vec(1,1,1)/2)
}
M++,N++
}
}
else{
runOnTick(1)
}
On=Pod["Active",number]
Cam["Activated",number]=On
if(!On & $On){
Pos=In
Ang=ang()
Foc=Range/4
Cam["Angle",angle]=Ang
}
elseif(On){
Po=Box:toWorld(Box:boxCenter())
Cam["Position",vector]=Po
W=Pod["W",number] S=Pod["S",number]
A=Pod["A",number] D=Pod["D",number]
#View direction rotation
if(A|D){
Ang+=ang(0,1,0)*(A-D)
Cam["Angle",angle]=Ang
}
Look=vec(1,0,0):rotate(Ang)
#Motion application
if(W|S){
Look*=5
Pos+=Look*(W-S)
Pos=rangerOffset(600,Pos,vec(0,0,-1)):position()+vec(0,0,64)
}
#User focus variation
Ne=Pod["NextWeapon",number] Pr=Pod["PrevWeapon",number]
Al=Pod["Alt",number]
if(Al){Foc=Range/4}
else{Foc+=Inc*(Ne-Pr)}
N=1
while(M<=T & N<=10){
#Location
H=PoA[M,vector]
H-=Pos*!Rel
R=(Distance ? H:length()/Range : 1)
H=H:normalized()*(Max*R)
holoPos(M,Box:toWorld(H))
if(RHit){
#Colouration
D=(255*R)
Ba=vec(1,1,1)*D
CE=Entities|Colour
if(EnA[M,entity] & CE){
E=EnA[M,entity]
if(Entities & !Colour){
if(E:type():lower():find("prop")){
holoColor(M,vec(0,1,0)*D)
}
elseif(E:isNPC()){holoColor(M,vec(1,0,0)*D)}
else{holoColor(M,Ba)}
}
elseif(Colour & !Entities){
Col=E:getColor()
holoColor(M,Col*D)
}
}
elseif(HiA[M,number]){holoColor(M,Ba)}
else{holoColor(M,vec())}
#Hit Normal
if(Norm){
An=HNA[M,vector]:toAngle()+ang(90,0,0)
holoAng(M,An)
}
else{holoAng(M,ang())}
}
M++,N++
}
if(M>=T){
M=1
PoA=Posit
HiA=Hit
HNA=HNorm
EnA=Ent
T=Posit:count()
}
}
Thursday, 4 February 2010
Plans for the Future
I'm always working around with some complex stuff, however simple I may think it is. One of the things I'm most known for though is drones, and figuring out control systems and such for them to function and work together co-operatively.
With the gBrain, I'm basically producing a basis for any drone to think and operate effectively without the inherent need for the player to actually figure it out beforehand. At most they just need a few basic ideas for what the gBrain should encourage the drone to do.
With the holographic arm I've been toying around with, which moves to be close to a target point by rotating it's joints as little as possible, I'm making a basis for you to make chassise with few components that move around with applyTorque applied to ballsockets, meaning you can have more human-sized and simplistic drones.
And very recently I've been looking at different patterns and styles for creating an effective "eye" for a drone to gather information about it's environment through, including a simple light spread method and a more complex focal point-based version mirroring the human eye's function. This means you can add it to a drone and it'll already have a strong idea of what's going on around it.
So the full picture should perhaps come into frame now. I don't expect to make a fully-functional science-fiction AI in Garry's Mod, but if I can eventually put together a little humanoid or whatever robot that I can plug gBrain into with a basic identification process for the holo-arm applyTorque joint system and a pair or so of eyes and have it run along without me needing to interfere with it at all, I'll be a very happy man.
I've made contraptions that almost every gmodder has made, I've made some very neat and sometimes primitive (in hindsight at least) drones, and I've toyed with the basis processes of the gmod physics engine and the real universe.
I think the next step for Garry's Mod contraptions is to make them self-sufficient. It's time we turned over the reigns to the contraptions themselves and took the seat of God in this virtual world. I think it's time we, the players, became the observers more than the builders.
And what's more, I think our ability to do so is right around the corner.
With the gBrain, I'm basically producing a basis for any drone to think and operate effectively without the inherent need for the player to actually figure it out beforehand. At most they just need a few basic ideas for what the gBrain should encourage the drone to do.
With the holographic arm I've been toying around with, which moves to be close to a target point by rotating it's joints as little as possible, I'm making a basis for you to make chassise with few components that move around with applyTorque applied to ballsockets, meaning you can have more human-sized and simplistic drones.
And very recently I've been looking at different patterns and styles for creating an effective "eye" for a drone to gather information about it's environment through, including a simple light spread method and a more complex focal point-based version mirroring the human eye's function. This means you can add it to a drone and it'll already have a strong idea of what's going on around it.
So the full picture should perhaps come into frame now. I don't expect to make a fully-functional science-fiction AI in Garry's Mod, but if I can eventually put together a little humanoid or whatever robot that I can plug gBrain into with a basic identification process for the holo-arm applyTorque joint system and a pair or so of eyes and have it run along without me needing to interfere with it at all, I'll be a very happy man.
I've made contraptions that almost every gmodder has made, I've made some very neat and sometimes primitive (in hindsight at least) drones, and I've toyed with the basis processes of the gmod physics engine and the real universe.
I think the next step for Garry's Mod contraptions is to make them self-sufficient. It's time we turned over the reigns to the contraptions themselves and took the seat of God in this virtual world. I think it's time we, the players, became the observers more than the builders.
And what's more, I think our ability to do so is right around the corner.
Thursday, 28 January 2010
Gravity Simulator
I was recently struck by the idea that everything in the universe is bound by gravity on a grand (Planets, stars) and minute (Atoms, molecules) scale, so I figured I'd do a little test on simulating gravity in Gmod.
I started off with a bunch of randomly-generated planets and a "sun" which dragged the planets towards it gradually (killing any that got too close), however I noticed that this didn't produce anything like the orbits we see in normal solar systems. However, I did discover by accident when I shifted the origin point of the sun (The E2 chip) that the planets spontaneously acquired a regular circular orbit.
So my next attempt was focused more on roaming particles, with each every element moving and pulling on and being pulled by every other element. How strongly each element was pulled depended of course on the density of the other element. So denser elements could very easily draw in less-dense elements, or very gradually pull together with other dense elements. ("Elements" here refers purely to a simulated object, not actual periodic elements of stars or such)
The way gravity works in this simulator is by directional velocity. Each element starts with an initial velocity, and then each other element pulls the element towards itself by an amount determined based on the element's density and distance by adding the subsequent vector to the element's velocity. As the simulator runs, the elements each move according to their new velocities, causing their directional pulls and positions to change, resulting in some rather surprising displays.
In this simple simulator, I've seen systems occur that resembled binary star systems, that look like ordinary one-star solar systems (As little elements whizz around a dense element), and even different elements pulling together to form a stronger collective gravity well, potentially forming heavier (periodic) elements or stars and black holes, depending on how you interpret the simulation.
And now, the code:
@name Gravity simulator
@persist Units U Base:vector RV
@persist Gr G State Slimit Dist
if(first()){
gSetGroup("atoms")
gDeleteAll()
U=1
Units=10 #Number of units to simulate
Slimit=75 #Speed limit of all units
Base=entity():pos() #Starting point for generation
Dist=1 #Gravity weaken over distance?
RV=1 #Randomised initial velocity
#If disabled, begins travelling from base point
}
if(U<=Units){interval(1000)}
else{runOnTick(1)}
M=0
#Unit generation
while(U<=Units & M<=10){
#Initial position
Vec=randvec()*random(0,1000)
Vec+=Base
#Initial Velocity
if(RV){
Vel=randvec()*random(0,Slimit)
}
else{
Vel=(Base-Vec):normalized()*min(Slimit,Vec:length())
}
gSetVec(U,Vel)
#Density
Den=round(random(0,255))
holoCreate(U,Vec,vec(1,1,1)*2)
holoModel(U,"hqicosphere2")
holoColor(U,vec(1,1,1)*Den)
holoAng(U,Vel:toAngle())
holoEntity(U):setTrails(5,5,10,"trails/plasma",vec(0,1,1)*255,255)
M++,U++
}
#Gravity function
if(tickClk()){
Une=holoEntity(Gr)
Vel=gGetVec(Gr)
Mass=Une:getColor():x()
Vec=Une:pos()
while(G<=Units & M<=30){
Ent=holoEntity(G)
if(G!=Gr & Ent){
Pos=Ent:pos()
Dis=Vec:distance(Pos)
#Gravity elements
Den=Ent:getAlpha() #Density
Rel=(Une:pos()-Pos):normalized() #Relative direction
Vel-=(Rel*(Den/Mass))*(Dist ? (1-min(Dis/700,1)) : 1)
gSetVec(Gr,Vel:normalized()*min(Vel:length(),Slimit))
#Heat
if(Dis<=100){
D=1-(Dis/100)
Hot=vec(1,1,0)*D
#holoColor(Gr,Une:getColor()+Hot)
}
}
M++,G++
}
if(G>=Units){
NP=Une:pos()+Vel
holoPos(Gr,NP)
holoAng(Gr,Vel:toAngle())
Gr=(Gr>=Units ? 1 : Gr+1)
G=0
}
}
I started off with a bunch of randomly-generated planets and a "sun" which dragged the planets towards it gradually (killing any that got too close), however I noticed that this didn't produce anything like the orbits we see in normal solar systems. However, I did discover by accident when I shifted the origin point of the sun (The E2 chip) that the planets spontaneously acquired a regular circular orbit.
So my next attempt was focused more on roaming particles, with each every element moving and pulling on and being pulled by every other element. How strongly each element was pulled depended of course on the density of the other element. So denser elements could very easily draw in less-dense elements, or very gradually pull together with other dense elements. ("Elements" here refers purely to a simulated object, not actual periodic elements of stars or such)
The way gravity works in this simulator is by directional velocity. Each element starts with an initial velocity, and then each other element pulls the element towards itself by an amount determined based on the element's density and distance by adding the subsequent vector to the element's velocity. As the simulator runs, the elements each move according to their new velocities, causing their directional pulls and positions to change, resulting in some rather surprising displays.
In this simple simulator, I've seen systems occur that resembled binary star systems, that look like ordinary one-star solar systems (As little elements whizz around a dense element), and even different elements pulling together to form a stronger collective gravity well, potentially forming heavier (periodic) elements or stars and black holes, depending on how you interpret the simulation.
And now, the code:
@name Gravity simulator
@persist Units U Base:vector RV
@persist Gr G State Slimit Dist
if(first()){
gSetGroup("atoms")
gDeleteAll()
U=1
Units=10 #Number of units to simulate
Slimit=75 #Speed limit of all units
Base=entity():pos() #Starting point for generation
Dist=1 #Gravity weaken over distance?
RV=1 #Randomised initial velocity
#If disabled, begins travelling from base point
}
if(U<=Units){interval(1000)}
else{runOnTick(1)}
M=0
#Unit generation
while(U<=Units & M<=10){
#Initial position
Vec=randvec()*random(0,1000)
Vec+=Base
#Initial Velocity
if(RV){
Vel=randvec()*random(0,Slimit)
}
else{
Vel=(Base-Vec):normalized()*min(Slimit,Vec:length())
}
gSetVec(U,Vel)
#Density
Den=round(random(0,255))
holoCreate(U,Vec,vec(1,1,1)*2)
holoModel(U,"hqicosphere2")
holoColor(U,vec(1,1,1)*Den)
holoAng(U,Vel:toAngle())
holoEntity(U):setTrails(5,5,10,"trails/plasma",vec(0,1,1)*255,255)
M++,U++
}
#Gravity function
if(tickClk()){
Une=holoEntity(Gr)
Vel=gGetVec(Gr)
Mass=Une:getColor():x()
Vec=Une:pos()
while(G<=Units & M<=30){
Ent=holoEntity(G)
if(G!=Gr & Ent){
Pos=Ent:pos()
Dis=Vec:distance(Pos)
#Gravity elements
Den=Ent:getAlpha() #Density
Rel=(Une:pos()-Pos):normalized() #Relative direction
Vel-=(Rel*(Den/Mass))*(Dist ? (1-min(Dis/700,1)) : 1)
gSetVec(Gr,Vel:normalized()*min(Vel:length(),Slimit))
#Heat
if(Dis<=100){
D=1-(Dis/100)
Hot=vec(1,1,0)*D
#holoColor(Gr,Une:getColor()+Hot)
}
}
M++,G++
}
if(G>=Units){
NP=Une:pos()+Vel
holoPos(Gr,NP)
holoAng(Gr,Vel:toAngle())
Gr=(Gr>=Units ? 1 : Gr+1)
G=0
}
}
Tuesday, 26 January 2010
The Gbrain - Data storage
There's many ways to store information in Wiremod, chief among them being tables & arrays in expression 2. The Gbrain neurons however have a serious memory issue.
The rule of thumb is that the fewers variables a given neuron looks at, the less combinations there can be, and therefore the less information it needs to store. However, even then, with any significant complexity, the neurons start running into tables the size of Big Ben (That's the big London clocktower, by the by, not Ben the morbidly obese guy on the subway).
Therefore, we need to consider ways to store and manage the data.
One possibility is to store each sequence of relevant variables and then have a glon-encoded table for each encountered action. An action that has not been encountered begins with a modifier of zero, of course, so it is irrelevant at that point as it should be, and then amended following consequence evaluation. This does little to reduce the memory footprint, but it does mean we don't need a whole new entry each time a different action is undertaken with previously-encountered circumstance.
Another is to have a super-array, containing an ordered sequence of glon-encoded tables which are unpacked and checked for the relevant entry. This means the memory footprint is a fraction of the overall data, and the neuron can be easily coded to loop through the different tables until it finds the right entry (if any) and to make a new table once the most recent one reaches a critical entry mass, storing it in the next entry of the super-array. The downside however is that, as the number of stored tables increases, so does the time it takes the neuron to search for the entry, whether or not the neuron has actually encountered the circumstance.
This latter option is probably what I'll be using for Gbrain, probably hybridised with the former to reduce the footprint further. The amount of time it spends looking in a table that lacks the circumstance is so minimal it could almost be said to never have happened.
Management of the data predominantly refers to how you search through it to find what you need, but since I've covered that above I'll more right to cutting down repeats and such. Good coding means you'll never have the same entry twice, so cutting down instead refers here to removing entries that are no-longer relevant or needed. Management of the database is necessary because of some easy math we can do right now.
Let's say we start off wholesale with 10,000 entries, determining the different actions and circumstances associated. Next, let's assume for each circumstance we've considered each action at least once, and that there are 5 actions (Left, right, forward, back, turn etc.). Then, in our stage 2 circumstance tables, let's assume we have about 50 entries each. Therefore overall we have (10000/5)/50 entries in the super-array. That's 40 entries, not much until you consider that for all the irrelevant or otherwise meaningless entries we still have to sort through all 40 of them when looking for previous data, that means each entry is more time it takes for an action to be considered or added. Culling the herd makes the gBrain faster and less memory heavy.
This is a tricky thing, because you're essentially programming "forgetfulness" into your system, which is an odd idea at the best of times. The best way I can think of is to include a reference array of the entries and when they were last modified. Then, every so often, run through the reference array checking the oldest entries. Obviously if we flat-out erased the oldest data, we could "forget" not to stick our hands in an open flame or to take a blind leap off a cliff.
Instead, before outright deleting something, instead first consider it's modifier. A strong modifier either way (Yes/No) should mean that this is something the brain has encountered often enough to decide firmly what to do in that situation, meaning something it definitively needs to do or avoid doing. This is an entry we don't want to lose, because we would then risk future operation safety.
Therefore instead delete entries with modifiers closer to zero on both counts, these are ones we've either encountered little or that are reasonably neutral on going either way anyway. Remember: Creating a new entry is resource-free, as is not finding an entry in the database. And the smaller the database is kept, the faster the neuron can decide what to do.
The rule of thumb is that the fewers variables a given neuron looks at, the less combinations there can be, and therefore the less information it needs to store. However, even then, with any significant complexity, the neurons start running into tables the size of Big Ben (That's the big London clocktower, by the by, not Ben the morbidly obese guy on the subway).
Therefore, we need to consider ways to store and manage the data.
One possibility is to store each sequence of relevant variables and then have a glon-encoded table for each encountered action. An action that has not been encountered begins with a modifier of zero, of course, so it is irrelevant at that point as it should be, and then amended following consequence evaluation. This does little to reduce the memory footprint, but it does mean we don't need a whole new entry each time a different action is undertaken with previously-encountered circumstance.
Another is to have a super-array, containing an ordered sequence of glon-encoded tables which are unpacked and checked for the relevant entry. This means the memory footprint is a fraction of the overall data, and the neuron can be easily coded to loop through the different tables until it finds the right entry (if any) and to make a new table once the most recent one reaches a critical entry mass, storing it in the next entry of the super-array. The downside however is that, as the number of stored tables increases, so does the time it takes the neuron to search for the entry, whether or not the neuron has actually encountered the circumstance.
This latter option is probably what I'll be using for Gbrain, probably hybridised with the former to reduce the footprint further. The amount of time it spends looking in a table that lacks the circumstance is so minimal it could almost be said to never have happened.
Management of the data predominantly refers to how you search through it to find what you need, but since I've covered that above I'll more right to cutting down repeats and such. Good coding means you'll never have the same entry twice, so cutting down instead refers here to removing entries that are no-longer relevant or needed. Management of the database is necessary because of some easy math we can do right now.
Let's say we start off wholesale with 10,000 entries, determining the different actions and circumstances associated. Next, let's assume for each circumstance we've considered each action at least once, and that there are 5 actions (Left, right, forward, back, turn etc.). Then, in our stage 2 circumstance tables, let's assume we have about 50 entries each. Therefore overall we have (10000/5)/50 entries in the super-array. That's 40 entries, not much until you consider that for all the irrelevant or otherwise meaningless entries we still have to sort through all 40 of them when looking for previous data, that means each entry is more time it takes for an action to be considered or added. Culling the herd makes the gBrain faster and less memory heavy.
This is a tricky thing, because you're essentially programming "forgetfulness" into your system, which is an odd idea at the best of times. The best way I can think of is to include a reference array of the entries and when they were last modified. Then, every so often, run through the reference array checking the oldest entries. Obviously if we flat-out erased the oldest data, we could "forget" not to stick our hands in an open flame or to take a blind leap off a cliff.
Instead, before outright deleting something, instead first consider it's modifier. A strong modifier either way (Yes/No) should mean that this is something the brain has encountered often enough to decide firmly what to do in that situation, meaning something it definitively needs to do or avoid doing. This is an entry we don't want to lose, because we would then risk future operation safety.
Therefore instead delete entries with modifiers closer to zero on both counts, these are ones we've either encountered little or that are reasonably neutral on going either way anyway. Remember: Creating a new entry is resource-free, as is not finding an entry in the database. And the smaller the database is kept, the faster the neuron can decide what to do.
Monday, 25 January 2010
Gmod Brain #2
Here's the basic construct I'm looking at for a given neuron.
When the control center sends out a "decision" signal, the neuron checks the incoming relevant variables from global variables, the precise number of which it gets from gGetNum(1) then counts upwards from there. For example, if the 1st gVar tells the neuron that there are 8 variables to acquire, it gets gVars 2-9 and stores them in together in a string separated by "-". gVar(2) is always the action to be undertaken in a string dialect (Such as "turnLeft", "turnRight", "Reverse", etc.)
The neuron then checks it's existing storehouse table of variable strings, to see if it's encountered the same circumstances before. It adds the modifier from that string reference to it's eventual result.
What occurs next is basically a random reaction. The neuron generates a random number from 0 to 1, adds the modifier, and rounds to 0 or 1 exactly. If it gets a 1, it sends out a "good" signal, and if it gets a 0, it sends out a "bad" signal.
The control center, which sets the number in gVar 1 and collects all the relevant good/bad signals, then tallies which state wins out and informs the operating body.
Meanwhile, the neuron waits until either it receives another "decision" signal or it receives a "pos" or "neg" signal. If it gets a "neg", it subtracts -0.1 (for example) from the stored modifier for the variable string in the storehouse, and adds 0.1 if it instead gets a "pos". Therefore, the neuron becomes less likely (but not unable) to choose the same bad idea twice and more likely to choose the same good idea twice.
Of course, this isn't by any means a perfect simulation of organic thinking, because in the words of Carl Sagan "The brain does more than just recollect, it intercompares, it synthesises, it analyses, it generates abstractions."
That of course is the next stage, once the neurons and control center can be properly composed for separate action types like motion and pathfinding etc.
When the control center sends out a "decision" signal, the neuron checks the incoming relevant variables from global variables, the precise number of which it gets from gGetNum(1) then counts upwards from there. For example, if the 1st gVar tells the neuron that there are 8 variables to acquire, it gets gVars 2-9 and stores them in together in a string separated by "-". gVar(2) is always the action to be undertaken in a string dialect (Such as "turnLeft", "turnRight", "Reverse", etc.)
The neuron then checks it's existing storehouse table of variable strings, to see if it's encountered the same circumstances before. It adds the modifier from that string reference to it's eventual result.
What occurs next is basically a random reaction. The neuron generates a random number from 0 to 1, adds the modifier, and rounds to 0 or 1 exactly. If it gets a 1, it sends out a "good" signal, and if it gets a 0, it sends out a "bad" signal.
The control center, which sets the number in gVar 1 and collects all the relevant good/bad signals, then tallies which state wins out and informs the operating body.
Meanwhile, the neuron waits until either it receives another "decision" signal or it receives a "pos" or "neg" signal. If it gets a "neg", it subtracts -0.1 (for example) from the stored modifier for the variable string in the storehouse, and adds 0.1 if it instead gets a "pos". Therefore, the neuron becomes less likely (but not unable) to choose the same bad idea twice and more likely to choose the same good idea twice.
Of course, this isn't by any means a perfect simulation of organic thinking, because in the words of Carl Sagan "The brain does more than just recollect, it intercompares, it synthesises, it analyses, it generates abstractions."
That of course is the next stage, once the neurons and control center can be properly composed for separate action types like motion and pathfinding etc.
Subscribe to:
Posts (Atom)
