This commit is contained in:
Vincent Loh 2024-05-20 19:22:57 -04:00
parent 37d053d9c2
commit 069705a31e
9 changed files with 907 additions and 8 deletions

View File

@ -1 +0,0 @@
../phys/anchor.js

76
anchor.js Normal file
View File

@ -0,0 +1,76 @@
class Anchor{
constructor(opt={}){
this.p=opt.p||[0,0,0]
this.a=opt.a||[[1,0,0],[0,1,0]]
this.r=opt.r||[[0,0,0],[0,0,0]]
this.v=opt.v||[0,0,0]
this.forces=[[0,0,0],[0,0,0],[0,0,0]]
this.pr={p:[...this.p],a:[[...this.a[0]],[...this.a[1]],vcross(...this.a)]}
}
update(dt=1){
this.pr={p:[...this.p],a:[[...this.a[0]],[...this.a[1]],vcross(...this.a)]}
for(let i=0;i<2;i++){for(let j=0;j<3;j++){this.r[i][j]+=this.forces[i][j]*dt}}
for(let z=0;z<3;z++){this.v[z]+=this.forces[2][z]*dt}
this.forces=[[0,0,0],[0,0,0],[0,0,0]]
for(let i=0;i<2;i++){for(let j=0;j<3;j++){this.r[i][j]*=cnst.drag.rot;if(i)this.v[j]*=cnst.drag.mov}}
for(let z=0;z<3;z++){this.p[z]+=this.v[z]*dt}
let b=[[...this.a[0]],[...this.a[1]],vcross(...this.a)]
for(let i=0;i<2;i++){for(let j=0;j<3;j++){for(let z=0;z<3;z++){this.a[i][z]+=this.r[i][j]*b[j][z]*dt}}}
this.a[1]=vnorm(vmultf(vcross(this.a[0],vcross(...this.a)),-1))
this.a[0]=vnorm(this.a[0])
}
applyForce(p,d){
this.forces[0][1]-=cnst.inertia.rot*p[1]*d[0]
this.forces[0][1]+=cnst.inertia.rot*p[0]*d[1]
this.forces[0][2]-=cnst.inertia.rot*p[2]*d[0]
this.forces[0][2]+=cnst.inertia.rot*p[0]*d[2]
this.forces[1][0]-=cnst.inertia.rot*p[1]*d[0]
this.forces[1][0]+=cnst.inertia.rot*p[0]*d[1]
this.forces[1][2]-=cnst.inertia.rot*p[2]*d[1]
this.forces[1][2]+=cnst.inertia.rot*p[1]*d[2]
let a=[...this.a,vcross(...this.a)]
for(let i=0;i<3;i++){for(let z=0;z<3;z++){this.forces[2][i]+=cnst.inertia.mov*d[z]*a[z][i]}}
}
applyWorldForce(p,d){
this.applyForce(toGrid(p),absolNormal(d))
}
//clarify: toGrid() is used to convert from world-space to internal grid space, fromGrid() is used to convert from internal grid space back to world space
toGrid(p){
let a=[p[0]-this.p[0],p[1]-this.p[1],p[2]-this.p[2]]
return [vdot(a,this.a[0]),vdot(a,this.a[1]),vdot(a,vcross(...this.a))]
}
fromGrid(p){
let a=[...this.p],b=[...this.a,vcross(...this.a)]
for(let z=0;z<3;z++){for(let i=0;i<3;i++){a[i]+=b[z][i]*p[z]}}
return a
}
//clarify: absolNormal() converts a world-space vector to a grid-space vector, for example you can use to apply a constant upwards force independent of rotation
//gridNormal() can be used to display a grid-space vector in world-space
absolNormal(p){
return [vdot(p,this.a[0]),vdot(p,this.a[1]),vdot(p,vcross(...this.a))]
}
gridNormal(p){
let a=[0,0,0],b=[...this.a,vcross(...this.a)]
for(let z=0;z<3;z++){for(let i=0;i<3;i++){a[i]+=b[z][i]*p[z]}}
return a
}
pr_toGrid(p){
let a=[p[0]-this.pr.p[0],p[1]-this.pr.p[1],p[2]-this.pr.p[2]]
return [vdot(a,this.pr.a[0]),vdot(a,this.pr.a[1]),vdot(a,vcross(...this.pr.a))]
}
pr_fromGrid(p){
let a=[...this.pr.p],b=[...this.pr.a,vcross(...this.pr.a)]
for(let z=0;z<3;z++){for(let i=0;i<3;i++){a[i]+=b[z][i]*p[z]}}
return a
}
pr_absolNormal(p){
return [vdot(p,this.pr.a[0]),vdot(p,this.pr.a[1]),vdot(p,vcross(...this.pr.a))]
}
pr_gridNormal(p){
let a=[0,0,0],b=[...this.pr.a,vcross(...this.pr.a)]
for(let z=0;z<3;z++){for(let i=0;i<3;i++){a[i]+=b[z][i]*p[z]}}
return a
}
}

View File

@ -1 +0,0 @@
../phys/craft.js

455
craft.js Normal file
View File

@ -0,0 +1,455 @@
class Craft{
constructor(opt={}){
this.fix=opt.fix||[]
this.edges=opt.edges||[]
this.faces=opt.faces||[]
this.joints=opt.joints||[]
this.complexEdges=[]
this.complexFaces=[]
this.p_jt=[[],[],[]]
this.o_jt=[[],[],[]]
this.fc=[]
this.weight=0;this.m_center=[0,0,0]
this.reactive=false
this.reactSetup=opt.reactive||{}
this.reactSetup.generateFriction=this.reactSetup.generateFriction||'vmultf(vadd(this.anchor.pr_fromGrid(this.p_fix[psz[i][2]]),vmultf(this.anchor.fromGrid(psz[i][0]),-1)),1)'
}
addFixture(p,c={},joint=-1){
if(!(this.joints[joint]||joint<0))return -1
let y=p,jt=joint,t=0,x=this.joints.length
while(t<x){
t++
y=vadd(y,jt<0?[0,0,0]:vmultf(this.joints[jt].offset,-1))
if(jt<0)break
else jt=this.joints[jt].joint
}
this.fix.push({joint:joint,grip:c.grip||cnst.grip,rebound:c.rebound||cnst.rebound,slide:c.slide||cnst.slide,offset:y,ptCol:['#FF0',-1]})
return this.fix.length-1
}
addJoint(p,axis=[0,1,0],joint=-1,opt={}){
if(!(this.joints[joint]||joint<0))return -1
let y=p,jt=joint,t=0,x=this.joints.length
while(t<x){
t++
y=vadd(y,jt<0?[0,0,0]:vmultf(this.joints[jt].offset,-1))
if(jt<0)break
else jt=this.joints[jt].joint
}
this.joints.push({t:0,joint:joint,axis:axis,offset:y,friction:opt.friction||10,stiff:opt.stiff||0.7,a:new Anchor(),weight:0,m_center:[0,0,0],chain:[]})
return this.joints.length-1
}
addEdge(a,b,w=1,c='#FF0'){
this.edges.push({p:[a,b],w:w,c:c})
if(this.fix[a].ptCol[1]<w)this.fix[a].ptCol=[c,w]
if(this.fix[b].ptCol[1]<w)this.fix[b].ptCol=[c,w]
return this.edges.length-1
}
addFace(a,b,c,w=1,n=0,s="#BBB"){
this.faces.push({p:[a,b,c],w:w,n:n,c:s,offset:[0,0,0],norm:[0,0,0],force:0})
return this.faces.length-1
}
setup(){
this.reactive=new Reactive(this.reactSetup)
this.reactive.returnCollides=1
let x=this.edges.length
for(let i=0;i<x;i++){
if(this.fix[this.edges[i].p[0]].joint==this.fix[this.edges[i].p[1]].joint){
if(this.fix[this.edges[i].p[0]].joint<0){
let a=vmag(vadd(this.fix[this.edges[i].p[0]].offset,vmultf(this.fix[this.edges[i].p[1]].offset,-1)))*this.edges[i].w
this.weight+=a
this.m_center=vadd(this.m_center,vmultf(vadd(this.fix[this.edges[i].p[0]].offset,this.fix[this.edges[i].p[1]].offset),a/2))
}else{
let a=vmag(vadd(this.fix[this.edges[i].p[0]].offset,vmultf(this.fix[this.edges[i].p[1]].offset,-1)))*this.edges[i].w
this.joints[this.fix[this.edges[i].p[0]].joint].weight+=a
this.joints[this.fix[this.edges[i].p[0]].joint].m_center=vadd(this.joints[this.fix[this.edges[i].p[0]].joint].m_center,
vmultf(vadd(this.fix[this.edges[i].p[0]].offset,this.fix[this.edges[i].p[1]].offset),a/2))
}
}else{
this.complexEdges.push(this.edges[i])
}
}
x=this.faces.length
for(let i=0;i<x;i++){
if(this.fix[this.faces[i].p[0]].joint==this.fix[this.faces[i].p[1]].joint&&this.fix[this.faces[i].p[2]].joint==this.fix[this.faces[i].p[1]].joint){
if(this.fix[this.faces[i].p[0]].joint<0){
let a=(
vmag(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))*
vmag(vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))*
Math.sqrt(1-sqrx(vdot(
vnorm(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1))),
vnorm(vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))
)))
)/8
this.faces[i].force=a*this.faces[i].n
this.faces[i].norm=vnorm(vcross(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)),vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1))))
this.faces[i].offset=vmultf(vadd(
this.fix[this.faces[i].p[0]].offset,vadd(this.fix[this.faces[i].p[1]].offset,this.fix[this.faces[i].p[2]].offset)
),1/3)
this.weight+=a
this.m_center=vadd(this.m_center,vmultf(vadd(
this.fix[this.faces[i].p[0]].offset,vadd(this.fix[this.faces[i].p[1]].offset,this.fix[this.faces[i].p[2]].offset)
),a*this.faces[i].w/3))
}else{
let a=(
vmag(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))*
vmag(vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))*
Math.sqrt(1-sqrx(vdot(
vnorm(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1))),
vnorm(vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)))
)))
)/8
this.faces[i].force=a*this.faces[i].n
this.faces[i].norm=vnorm(vcross(vadd(this.fix[this.faces[i].p[0]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1)),vadd(this.fix[this.faces[i].p[2]].offset,vmultf(this.fix[this.faces[i].p[1]].offset,-1))))
this.faces[i].offset=vmultf(vadd(
this.fix[this.faces[i].p[0]].offset,vadd(this.fix[this.faces[i].p[1]].offset,this.fix[this.faces[i].p[2]].offset)
),1/3)
this.m_center=vadd(this.m_center,vmultf(vadd(
this.fix[this.faces[i].p[0]].offset,vadd(this.fix[this.faces[i].p[1]].offset,this.fix[this.faces[i].p[2]].offset)
),a*this.faces[i].w/3))
this.joints[this.fix[this.faces[i].p[0]].joint].weight+=a
this.joints[this.fix[this.faces[i].p[0]].joint].m_center=vadd(this.joints[this.fix[this.faces[i].p[0]].joint].m_center,vmultf(vadd(
this.fix[this.faces[i].p[0]].offset,vadd(this.fix[this.faces[i].p[1]].offset,this.fix[this.faces[i].p[2]].offset)
),a/3))
}
}else{
this.complexFaces.push(this.faces[i])
}
}
x=this.joints.length
for(let i=0;i<x;i++){
if(this.joints[i].weight)this.joints[i].m_center=vmultf(this.joints[i].m_center,1/this.joints[i].weight)
let c=0,z=this.joints[i].joint;this.joints[i].chain=[]
while(z>-1||c>x){
c++
this.joints[i].chain.push(z)
z=this.joints[z].joint
}
}
this.m_center=this.weight?vmultf(this.m_center,1/this.weight):[0,0,0]
this.weld()
this.weld()
this.reactive.p_fix=[]
x=this.reactive.fix.length
for(let i=0;i<x;i++){
this.fc.push(this.fix[i].ptCol[0])
this.reactive.p_fix.push(this.reactive.fix[i])
}
}
weld(){
this.reactive.p_fix=[]
let x=this.reactive.fix.length
for(let i=0;i<x;i++){
this.reactive.p_fix.push(this.reactive.fix[i])
}
this.reactive.fix=[]
this.reactive.edges=[...this.edges]
let jp=[],jr=[],offset=[0,0,0],axes=[[1,0,0],[0,1,0]],c,t
x=this.joints.length
for(let i=0;i<x;i++){
this.joints[i].a.p=[0,0,0]
this.joints[i].a.v=[0,0,0]
let z=this.joints[i].joint
offset=[...this.joints[i].offset]
axes=this.joints[i].a.a
c=0
while(z>-1&&c<x){
c++
if(jp[z]){
offset=vadd(jp[z],vtransform(jr[z],[0,0,0],offset))
axes=[vnorm(vtransform(jr[z],[0,0,0],axes[0])),vnorm(vtransform(jr[z],[0,0,0],axes[1]))]
break
}
offset=vadd(this.joints[z].offset,this.joints[z].a.gridNormal(offset))
axes=[this.joints[z].a.gridNormal(axes[0]),this.joints[z].a.gridNormal(axes[1])]
z=this.joints[z].joint
}
jp.push(offset)
jr.push(axes)
}
this.o_jt=[[],[],[...this.p_jt[2]]]
for(let i=0;i<this.p_jt[0].length;i++){
this.o_jt[0].push([...this.p_jt[0][i]])
}
for(let i=0;i<this.p_jt[1].length;i++){
this.o_jt[1].push([...this.p_jt[1][i]])
}
this.p_jt=[jp,jr]
x=this.fix.length
for(let i=0;i<x;i++){
if(this.fix[i].joint<0){
this.reactive.fix.push(this.fix[i].offset)
}else{
this.reactive.fix.push(vtransform(jr[this.fix[i].joint],jp[this.fix[i].joint],this.fix[i].offset))
}
}
offset=[0,0,0];c=0
x=this.joints.length
for(let i=0;i<x;i++){
c+=this.joints[i].weight
offset=vadd(offset,vmultf(vadd(jp[i],vtransform(jr[i],[0,0,0],this.joints[i].m_center)),this.joints[i].weight))
}
c+=this.weight
offset=vadd(offset,vmultf(this.m_center,this.weight))
offset=vmultf(offset,-1/c)
this.p_jt.push(offset)
x=this.reactive.fix.length
for(let i=0;i<x;i++){
this.reactive.fix[i]=vadd(this.reactive.fix[i],offset)
}
}
center(){
let offset=[0,0,0],c=0,x=this.joints.length
for(let i=0;i<x;i++){
c+=this.joints[i].weight
offset=vadd(offset,vmultf(vadd(this.p_jt[0][i],vtransform(this.p_jt[1][i],[0,0,0],this.joints[i].m_center)),this.joints[i].weight))
}
c+=this.weight
offset=vadd(offset,vmultf(this.m_center,this.weight))
offset=vmultf(offset,-1/c)
this.m_center=vadd(this.m_center,offset)
x=this.fix.length
for(let i=0;i<x;i++){
if(this.fix[i].joint<0)this.fix[i].offset=vadd(this.fix[i].offset,offset)
}
x=this.joints.length
for(let i=0;i<x;i++){
this.joints[i].offset=vadd(this.joints[i].offset,offset)
}
}
update(dt=1){
let t_weight=this.weight
for(let i=0;i<this.joints.length;i++){
t_weight+=this.joints[i].weight
}
for(let i=0;i<this.faces.length;i++){
if(this.faces[i].force){
let p=[0,0,0],op=[0,0,0],d=[0,0,0]
if(this.fix[this.faces[i].p[0]].joint<0){
p=this.faces[i].offset
op=this.faces[i].offset
d=this.faces[i].norm
}else{
p=vadd(this.p_jt[0][this.fix[this.faces[i].p[0]].joint],vtransform(this.p_jt[1][this.fix[this.faces[i].p[0]].joint],[0,0,0],this.faces[i].offset))
op=vadd(this.o_jt[0][this.fix[this.faces[i].p[0]].joint],vtransform(this.o_jt[1][this.fix[this.faces[i].p[0]].joint],[0,0,0],this.faces[i].offset))
d=vtransform(this.p_jt[1][this.fix[this.faces[i].p[0]].joint],[0,0,0],this.faces[i].norm)
op=vadd(p,vmultf(vadd(op,vmultf(p,-1)),20))
}
this.applyGlobForce(
this.fix[this.faces[i].p[0]].joint,
vadd(p,vmultf(this.p_jt[2],1)),
vmultf(d,vdot(
vadd(this.reactive.anchor.pr_fromGrid(vadd(op,vmultf(this.p_jt[2],-1))),vmultf(this.reactive.anchor.fromGrid(vadd(p,vmultf(this.p_jt[2],-1))),-1)),
this.reactive.anchor.gridNormal(d)
)*this.faces[i].force/t_weight*20)
)
}
}
this.weld()
if(this.reactive){
this.reactive.anchor.update(dt)
for(let i=0;i<this.joints.length;i++){
this.joints[i].a.update(dt)
if(this.joints[i].axis[0]!=0){
this.joints[i].a.a[0]=this.joints[i].axis
this.joints[i].a.a[1]=vsnap(this.joints[i].a.a[1],this.joints[i].axis)
}
if(this.joints[i].axis[1]!=0){
this.joints[i].a.a[1]=this.joints[i].axis
this.joints[i].a.a[0]=vsnap(this.joints[i].a.a[0],this.joints[i].axis)
}
if(this.joints[i].axis[2]!=0){
this.joints[i].a.a[0]=vsnap(this.joints[i].a.a[0],this.joints[i].axis)
this.joints[i].a.a[1]=vsnap(this.joints[i].a.a[1],this.joints[i].axis)
if(vmag(vadd(vcross(...this.joints[i].a.a),this.joints[i].axis))<1)this.joints[i].a.a[1]=vmultf(this.joints[i].a.a[1],-1)
}
}
}
}
applyJointForce(j,p2,d2){
let p=[...p2],d=[...d2]
if(this.joints[j]){
let axes=this.p_jt[1][j]
p=vtransform(axes,[0,0,0],p);d=vtransform(axes,[0,0,0],d)
let weightSum=this.weight,weightJoint=0,x=this.joints.length
for(let i=0;i<x;i++){
weightSum+=this.joints[i].weight
if(i==j||this.joints[i].chain.includes(j))weightJoint+=this.joints[i].weight
}
this.joints[j].a.applyForce(p2,vmultf(d2,weightJoint/weightSum/2-1/2))
this.joints[j].a.applyForce(vmultf(p2,-1),vmultf(d2,1/2-weightJoint/weightSum/2))
this.reactive.applyForce(p,vmultf(d,weightJoint/weightSum/2))
this.reactive.applyForce(vmultf(p,-1),vmultf(d,-weightJoint/weightSum/2))
}
}
/*
applyAbsolJointForce(j,p,d2){
let d=vsnap(vdetransform(
this.p_jt[1][j],
[0,0,0],
d2
),this.joints[j].axis)
this.applyJointForce(j,p,d)
}
applyWorldJointForce(j,p,d2){
let np=vsnap(vdetransform(
this.p_jt[1][j],
[0,0,0],
vadd(p,vadd(vmultf(this.p_jt[0][j],-1),vmultf(this.p_jt[2],-1)))
),this.joints[j].axis),
d=vsnap(vdetransform(
this.p_jt[1][j],
[0,0,0],
d2
),this.joints[j].axis)
this.applyJointForce(j,np,d)
}
*/
turnJoint(j,target2,reference2,force,damping){
if(!this.joints[j])return
if(this.joints[j].t==0){
let target=vmultf(vnorm(target2),-1),reference=vnorm(reference2)
this.applyJointForce(j,vsnap(reference,this.joints[j].axis),vmultf(vsnap(this.joints[j].a.absolNormal(target),this.joints[j].axis),force))
this.applyJointForce(j,this.joints[j].axis[0]?[0,1,0]:[1,0,0],vmultf(this.joints[j].a.r[this.joints[j].axis[0]?1:0],damping))
}
}
applyGlobForce(j,p,d){
let jt=j,c=0
while(jt>-1&&c<this.joints.length){
this.applyJointForce(
jt,
vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
vadd(p,vadd(vmultf(this.p_jt[0][jt],-1),vmultf(this.p_jt[2],-1)))
),this.joints[jt].axis),
vmultf(
vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
d
),this.joints[jt].axis),
-this.joints[jt].stiff
)
)
jt=this.joints[jt].joint
c++
}
this.reactive.anchor.applyForce(p,d)
}
collide(dt,p,d){
let dt2=1//(1+(dt-1)*3)
if(this.reactive){
let collides=this.reactive.collide(p,d)
if(!collides.length)return
let z=collides.length,n=[0]
for(let i=0;i<this.joints.length;i++){
n.push(0)
}
for(let i=0;i<z;i++){
n[this.fix[collides[i][0][2]].joint+1]++
}
for(let i=0;i<z;i++){
let rebound=this.fix[collides[i][0][2]].rebound,grip=this.fix[collides[i][0][2]].grip/dt,jt=this.fix[collides[i][0][2]].joint,c=0
while(jt>-1&&c<this.joints.length){
let np=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
vadd(collides[i][0][0],vadd(vmultf(this.p_jt[0][jt],-1),vmultf(this.p_jt[2],-1)))
),this.joints[jt].axis)
let fc=[
this.reactive.anchor.absolNormal(vmultf(collides[i][0][3],-collides[i][0][1][1]/z*rebound/cnst.inertia.mov)),
this.reactive.anchor.absolNormal(vmultf(collides[i][1],1/2/n[this.fix[collides[i][0][2]].joint+1]*grip/cnst.inertia.mov))
]
fc[0]=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
fc[0]
),this.joints[jt].axis)
fc[1]=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
fc[1]
),this.joints[jt].axis)
this.applyJointForce(jt,vnorm(np),vmultf(fc[0],-dt2*this.joints[jt].stiff))
this.applyJointForce(jt,vnorm(np),vmultf(fc[1],-dt2*dt2*this.joints[jt].stiff))
jt=this.joints[jt].joint
c++
}
this.reactive.anchor.applyForce(vnorm(collides[i][0][0]),this.reactive.anchor.absolNormal(vmultf(collides[i][0][3],-dt2*collides[i][0][1][1]/z*rebound/cnst.inertia.mov)))
this.reactive.anchor.applyForce(vnorm(collides[i][0][0]),this.reactive.anchor.absolNormal(vmultf(collides[i][1],dt2*dt2/2/n[this.fix[collides[i][0][2]].joint+1]*grip/cnst.inertia.mov)))
}
}
}
collidef(dt,g){
let dt2=1//(1+(dt-1)*3)
if(this.reactive){
let collides=this.reactive.collidef(g)
if(!collides.length)return
let z=collides.length,n=[0]
for(let i=0;i<this.joints.length;i++){
n.push(0)
}
for(let i=0;i<z;i++){
n[this.fix[collides[i][0][2]].joint+1]++
}
for(let i=0;i<z;i++){
let rebound=this.fix[collides[i][0][2]].rebound,grip=this.fix[collides[i][0][2]].grip,slide=this.fix[collides[i][0][2]].slide,jt=this.fix[collides[i][0][2]].joint,c=0
while(jt>-1&&c<this.joints.length){
let np=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
vadd(collides[i][0][0],vadd(vmultf(this.p_jt[0][jt],-1),vmultf(this.p_jt[2],-1)))
),this.joints[jt].axis)
let fc=[
this.reactive.anchor.absolNormal(vmultf(collides[i][0][3],-collides[i][0][1][1]/z*rebound/cnst.inertia.mov)),
this.reactive.anchor.absolNormal(vmultf(collides[i][1],1/2/n[this.fix[collides[i][0][2]].joint+1]*grip/cnst.inertia.mov))
]
fc[0]=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
fc[0]
),this.joints[jt].axis)
fc[1]=vsnap(vdetransform(
this.p_jt[1][jt],
[0,0,0],
fc[1]
),this.joints[jt].axis)
this.applyJointForce(jt,vnorm(np),vmultf(fc[0],-dt2*this.joints[jt].stiff))
this.applyJointForce(jt,vnorm(np),vmultf(fc[1],-dt2*dt2*this.joints[jt].stiff))
jt=this.joints[jt].joint
c++
}
this.reactive.anchor.applyForce(vnorm(collides[i][0][0]),this.reactive.anchor.absolNormal(vmultf(collides[i][0][3],-dt2*collides[i][0][1][1]/z*rebound/cnst.inertia.mov)))
this.reactive.anchor.applyForce(vnorm(collides[i][0][0]),this.reactive.anchor.absolNormal(vmultf(collides[i][0][3],vdot(collides[i][1],collides[i][0][3])/2/n[this.fix[collides[i][0][2]].joint+1]*grip/cnst.inertia.mov)))
this.reactive.anchor.applyForce(vnorm(collides[i][0][0]),this.reactive.anchor.absolNormal(vmultf(vsnap(collides[i][1],collides[i][0][3]),slide/2/n[this.fix[collides[i][0][2]].joint+1]*grip/cnst.inertia.mov)))
}
}
}
applyForce(p,d){
if(this.reactive)this.reactive.applyForce(p,d)
}
toGrid(p){
if(this.reactive)return this.reactive.toGrid(p)
}
fromGrid(p){
if(this.reactive)return this.reactive.fromGrid(p)
}
absolNormal(p){
if(this.reactive)return this.reactive.absolNormal(p)
}
gridNormal(p){
if(this.reactive)return this.reactive.gridNormal(p)
}
pr_toGrid(p){
if(this.reactive)return this.reactive.pr_toGrid(p)
}
pr_fromGrid(p){
if(this.reactive)return this.reactive.pr_fromGrid(p)
}
pr_absolNormal(p){
if(this.reactive)return this.reactive.pr_absolNormal(p)
}
pr_gridNormal(p){
if(this.reactive)return this.reactive.pr_gridNormal(p)
}
}

View File

@ -1 +0,0 @@
../phys/craft_models.js

73
craft_models.js Normal file
View File

@ -0,0 +1,73 @@
let sqr3 = Math.sqrt(3)
genCraftModels=craft=>{
let uid=newUID(),models=[[]]
models[0].push(new p5.Geometry(
1,1,
function createGeometry(){
for(let x=0;x<craft.faces.length;x++){
if(craft.fix[craft.faces[x].p[0]].joint==-1){
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[0]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[1]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[2]].offset))
this.uvs.push([0,0])
this.uvs.push([1,0])
this.uvs.push([1/2,1/2*sqr3])
this.faces.push([this.vertices.length-1,this.vertices.length-2,this.vertices.length-3])
}
}
this.computeNormals()
this.gid='craft-geometry-faces-'+uid
}
))
models[0].push(new p5.Geometry(
1,1,
function createGeometry(){
for(let x=0;x<craft.edges.length;x++){
if(craft.fix[craft.edges[x].p[0]].joint==-1){
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[0]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[1]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[1]].offset))
this.faces.push([this.vertices.length-1,this.vertices.length-2,this.vertices.length-3])
}
}
this.gid='craft-geometry-edges-'+uid
}
))
for(let i=0;i<craft.joints.length;i++){
models.push([])
models[i+1].push(new p5.Geometry(
1,1,
function createGeometry(){
for(let x=0;x<craft.faces.length;x++){
if(craft.fix[craft.faces[x].p[0]].joint==i){
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[0]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[1]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.faces[x].p[2]].offset))
this.uvs.push([0,0])
this.uvs.push([1,0])
this.uvs.push([1/2,1/2*sqr3])
this.faces.push([this.vertices.length-1,this.vertices.length-2,this.vertices.length-3])
}
}
this.computeNormals()
this.gid='craft-geometry-joint-faces-'+i.toString()+uid
}
))
models[i+1].push(new p5.Geometry(
1,1,
function createGeometry(){
for(let x=0;x<craft.edges.length;x++){
if(craft.fix[craft.edges[x].p[0]].joint==i){
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[0]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[1]].offset))
this.vertices.push(new p5.Vector(...craft.fix[craft.edges[x].p[1]].offset))
this.faces.push([this.vertices.length-1,this.vertices.length-2,this.vertices.length-3])
}
}
this.gid='craft-geometry-joint-edges-'+i.toString()+uid
}
))
}
return models
}

View File

@ -1 +0,0 @@
../phys/engine.js

0
engine.js Normal file
View File

View File

@ -1 +0,0 @@
../phys/index.html

14
index.html Normal file
View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/p5@1.9.3/lib/p5.js"></script>
</head>
<body>
<script src="utils.js"></script>
<script src="anchor.js"></script>
<script src="reactive.js"></script>
<script src="craft.js"></script>
<script src="craft_models.js"></script>
<script src="main.js"></script>
</body>
</html>

View File

@ -1 +0,0 @@
../phys/main.js

118
main.js Normal file
View File

@ -0,0 +1,118 @@
let reacts = [], dt = 1, paused = 1;
function setup(){
let craftdata = {
fix: [
[1, 1, 1], // 0
[1, 1, -1], // 1
[1, -1, 1], // 2
[1, -1, -1], // 3
[-1, 1, 1], // 4
[-1, 1, -1], // 5
[-1, -1, 1], // 6
[-1, -1, -1] // 7
],
edges: [
[0, 1],
[0, 2],
[0, 4],
[1, 3],
[1, 5],
[2, 3],
[2, 6],
[3, 7],
[4, 5],
[4, 6],
[5, 7],
[6, 7]
],
faces: [
[0, 1, 2],
[4, 6, 7],
]
};
for(let i=0; i<4; ++i){
reacts.push(new Craft());
for(let fix of craftdata.fix) reacts[reacts.length-1].addFixture(fix);
for(let edge of craftdata.edges) reacts[reacts.length-1].addEdge(...edge);
for(let face of craftdata.faces) reacts[reacts.length-1].addFace(...face);
reacts[reacts.length-1].setup();
reacts[reacts.length-1].reactive.anchor.p[1] = -5
reacts[reacts.length-1].applyForce([Math.random()*10-5, 0, Math.random()*10-5],
reacts[reacts.length-1].reactive.absolNormal([0, -Math.random()*10-20, 0]));
}
createCanvas(400, 400, WEBGL);
lt = Date.now();
}
function draw(){
background(200);
scale(10);
for(let i=0; i<10; ++i)
point(Math.cos(TWO_PI/10*i)*10, 4, Math.sin(TWO_PI/10*i)*10);
for(let react of reacts){
if(!paused){
react.applyForce([0, 0, 0], react.reactive.absolNormal([0, 8, 0]));
react.collide(1/dt, [0, 4, 0], [[0, 0, 1], [0, 1, 0]]);
react.update(1/dt);
}
push();
applyMatrix(mlookAt(react.reactive.anchor.p,react.reactive.anchor.a));
if(!react.models||!react.models.length)
react.models=genCraftModels(react)
strokeWeight(5);
push();
translate(...react.p_jt[2]);
fill(100, 100, 100); noStroke();
model(react.models[0][0]);
noFill(); stroke(255, 255, 0); strokeWeight(1);
model(react.models[0][1]);
pop();
for(let f=0; f<react.joints.length; ++f){
push();
applyMatrix(mlookAt(vadd(react.p_jt[0][f],react.p_jt[2]),react.p_jt[1][f]))
fill(100); noStroke();
model(react.models[f+1][0]);
noFill(); stroke(255, 255, 0); strokeWeight(1);
model(react.models[f+1][1]);
pop();
strokeWeight(1.5);
stroke(255, 0, 0);
point(...vadd(react.p_jt[0][f], react.p_jt[2]));
}
pop();
push();
push();
applyMatrix(mlookAt(react.reactive.anchor.p,react.reactive.anchor.a));
translate(react.p_jt[2][0], 4, react.p_jt[2][2]);
pop();
translate(-4.1*.3, 4.1, -4.1*.3);
applyMatrix([
1, 0, 0, 0,
.3, 0, .3, 0,
0, 0, 1, 0,
0, 0, 0, 1
]);
applyMatrix(mlookAt(react.reactive.anchor.p,react.reactive.anchor.a));
fill(150, 150, 150); noStroke();
model(react.models[0][0]);
noFill(); stroke(150, 150, 150); strokeWeight(1);
model(react.models[0][1]);
pop();
}
orbitControl();
}
function keyReleased(){
if(key == 'a') paused = !paused;
if(key == 's'){
paused = false; dt = 6 - dt;
}
}

2
mirror.sh Executable file
View File

@ -0,0 +1,2 @@
cp ~/misc/phys/*.js ./
cp ~/misc/phys/index.html ./

View File

@ -1 +0,0 @@
../phys/reactive.js

92
reactive.js Normal file
View File

@ -0,0 +1,92 @@
class Reactive{
constructor(opt={}){
this.anchor=new Anchor(opt.anchor||{})
this.fix=opt.fix||[]
this.edges=opt.edges||[]
this.returnCollides=opt.returnCollides||0
this.generateFriction=opt.generateFriction||'vadd(this.anchor.pr_fromGrid(psz[i][0]),vmultf(this.anchor.fromGrid(psz[i][0]),-1))'
}
collide(p,d2){
let collides=[],d=[vnorm(d2[0]),vnorm(d2[1])],psz=[],x,tc=0,getOrient=i=>{let x=vadd(i,vmultf(p,-1));return [vdot(x,d[0]),vdot(x,d[1]),vdot(x,vcross(...d))]}
for(let i=0;i<this.fix.length;i++){
x=getOrient(this.anchor.fromGrid(this.fix[i]))
let r=vmag(this.fix[i])
if(x[1]>0)psz.push([this.fix[i],vmultf(x,1/r),i,d[1],r])
}
for(let i=0;i<psz.length;i++){
let z=vmultf(eval(this.generateFriction),1/psz[i][4])
if(this.returnCollides){
collides.push([psz[i],z])
}else{
this.anchor.applyForce(vnorm(psz[i][0]),this.anchor.absolNormal(vmultf(d[1],-psz[i][1][1]/psz.length*cnst.rebound/cnst.inertia.mov)))
this.anchor.applyForce(vnorm(psz[i][0]),this.anchor.absolNormal(vmultf(z,1/psz.length*cnst.grip/cnst.inertia.mov)))
}
}
if(psz.length==0){
x=getOrient(this.anchor.p)
if(x[1]>0){
this.anchor.applyForce([0,0,0],this.anchor.absolNormal(vmultf([0,-x[1],0],cnst.rebound/cnst.inertia.mov)))
this.anchor.applyForce([0,0,0],this.anchor.absolNormal(vmultf(this.anchor.v,cnst.grip/cnst.inertia.mov)))
}
}
return collides
}
collidef(g/*generation function for input p, returns {p[3] position d[2][3] anchors}*/){
let collides=[],psz=[],x,tc=0,getOrient=i=>{let y=g(i);y.d=[vnorm(y.d[0]),vnorm(y.d[1])];x=vadd(i,vmultf(y.p,-1));return [vdot(x,y.d[0]),vdot(x,y.d[1]),vdot(x,vcross(...y.d))]}
for(let i=0;i<this.fix.length;i++){
x=getOrient(this.anchor.fromGrid(this.fix[i]))
let r=vmag(this.fix[i])
if(x[1]>0)psz.push([this.fix[i],vmultf(x,1/r),i,g(this.anchor.fromGrid(this.fix[i])).d[1],r])
}
for(let i=0;i<psz.length;i++){
let z=vmultf(eval(this.generateFriction),1/psz[i][4])
if(this.returnCollides){
collides.push([psz[i],z])
}else{
this.anchor.applyForce(vnorm(psz[i][0]),this.anchor.absolNormal(vmultf(psz[i][3],-psz[i][1][1]/psz.length*cnst.rebound/cnst.inertia.mov)))
this.anchor.applyForce(vnorm(psz[i][0]),this.anchor.absolNormal(vmultf(z,1/psz.length*cnst.grip/cnst.inertia.mov)))
}
}
if(psz.length==0){
x=getOrient(this.anchor.p)
if(x[1]>0){
this.anchor.applyForce([0,0,0],this.anchor.absolNormal(vmultf([0,-x[1],0],cnst.rebound/cnst.inertia.mov)))
this.anchor.applyForce([0,0,0],this.anchor.absolNormal(vmultf(this.anchor.v,cnst.grip/cnst.inertia.mov)))
}
}
return collides
}
update(dt=1){
this.anchor.update(dt)
}
applyForce(p,d){
this.anchor.applyForce(p,d)
}
applyWorldForce(p,d){
this.anchor.applyWorldForce(p,d)
}
toGrid(p){
return this.anchor.toGrid(p)
}
fromGrid(p){
return this.anchor.fromGrid(p)
}
absolNormal(p){
return this.anchor.absolNormal(p)
}
gridNormal(p){
return this.anchor.gridNormal(p)
}
pr_toGrid(p){
return this.anchor.pr_toGrid(p)
}
pr_fromGrid(p){
return this.anchor.pr_fromGrid(p)
}
pr_absolNormal(p){
return this.anchor.pr_absolNormal(p)
}
pr_gridNormal(p){
return this.anchor.pr_gridNormal(p)
}
}

View File

@ -1 +0,0 @@
../phys/utils.js

77
utils.js Normal file
View File

@ -0,0 +1,77 @@
const cnst={
inertia:{
rot:1/1000,
mov:1/1000
},
drag:{
rot:1,
mov:1
},
grip:0.7,
rebound:0.7,
slide:0.7
}
vdot=(a,b)=>a.map((x,i)=>a[i]*b[i]).reduce((m,n)=>m+n)
vcross=(a,b)=>[a[1]*b[2]-a[2]*b[1],a[2]*b[0]-a[0]*b[2],a[0]*b[1]-a[1]*b[0]]
vnorm=a=>{let b=Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);if(b==0)return [0,0,0];return [a[0]/b,a[1]/b,a[2]/b]}
vadd=(a,b)=>[a[0]+b[0],a[1]+b[1],a[2]+b[2]]
vmult=(a,b)=>[a[0]*b[0],a[1]*b[1],a[2]*b[2]]
vmultf=(a,b)=>[a[0]*b,a[1]*b,a[2]*b]
vmag=a=>Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2])
vsnap=(a,b)=>vadd(a,vmultf(b,-vdot(b,a)))
vtransform=(a,p,v)=>{
let x=[...p],b=[...a,vcross(...a)]
for(let z=0;z<3;z++){for(let i=0;i<3;i++){x[i]+=b[z][i]*v[z]}}
return x
}
vdetransform=(a,p,v)=>{
let x=[v[0]-p[0],v[1]-p[1],v[2]-p[2]]
return [vdot(x,a[0]),vdot(x,a[1]),vdot(x,vcross(...a))]
}
pointAt=(p,c)=>{
rnd.rotateY(-Math.atan2(p[2]-c[2],p[0]-c[0]))
rnd.rotateZ(Math.atan2(p[1]-c[1],Math.sqrt((p[0]-c[0])*(p[0]-c[0])+(p[2]-c[2])*(p[2]-c[2]))))
}
drawScreenAt=(p,c)=>{
rnd.translate(...p)
pointAt(p,c)
}
build_pointAt=(p,c)=>{
build_rnd.rotateY(-Math.atan2(p[2]-c[2],p[0]-c[0]))
build_rnd.rotateZ(Math.atan2(p[1]-c[1],Math.sqrt((p[0]-c[0])*(p[0]-c[0])+(p[2]-c[2])*(p[2]-c[2]))))
}
build_drawScreenAt=(p,c)=>{
build_rnd.translate(...p)
build_pointAt(p,c)
}
let currUID=0
newUID=_=>{
currUID++
return 'UID_'+currUID.toString()
}
keyToCode=(dir,n)=>{
let mapTable=(`RIGHT 39_LEFT 37_DOWN 40_UP 38_SHIFT 16_SPACE 32_0 48_1 49_2 50_3 51_4 52_5 53_6 54_7 55_8 56_9 57_a 65_b 66_c 67_d 68_e 69_f 70_g 71_h 72_i 73_j 74_k 75_l 76_m 77_n 78_o 79_p 80_q 81_r 82_s 83_t 84_u 85_v 86_w 87_x 88_y 89_z 90`).split('_')
if(dir=='key'){for(let i=0;i<mapTable.length;i++){if(mapTable[i].endsWith(n.toString()))return mapTable[i].split(' ')[0]}}
else if(dir=='code'){for(let i=0;i<mapTable.length;i++){if(mapTable[i].startsWith(n))return parseInt(mapTable[i].slice(mapTable[i].length-2,mapTable[i].length))}}
return 0
}
mlookAt=(p2,a2)=>{
let a=[],p=[...p2]
a.push([...a2[0]],[...a2[1]])
a.push(vcross(...a))
a[0]=vnorm(a[0])
a[1]=vnorm(a[1])
return [...a[0],0,...a[1],0,...a[2],0,...p,1]
}
line3d=(r,a,b,w=0.03)=>{
r.push()
let x=[vnorm(vadd(a,vmultf(b,-1)))],c=vmultf(vadd(a,b),1/2)
if(x[0][0]>0.9)x.push([0,1,0])
else x.push([1,0,0])
x[1]=vnorm(vcross(...x))
x.push(vnorm(vcross(...x)))
r.applyMatrix([...x[0],0,...x[1],0,...x[2],0,...c,1])
r.box(vmag(vadd(a,vmultf(b,-1))),w,w)
r.pop()
}
sqrx=a=>a*a