# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# $Id: beziercurvegeom.py,v 1.2 2005/09/02 12:58:02 mbaas Exp $
from _OpenGL.GL import *
import bisect
from geomobject import *
from slots import *
from cgtypes import *
from boundingbox import BoundingBox
import protocols
from ribexport import IGeometry
from ri import *
# BezierPoint
class BezierPoint:
"""A single Bezier point with tangents.
This object is used during construction of a BezierCurveGeom object.
"""
def __init__(self, pos, intangent=vec3(0), outtangent=vec3(0)):
self.pos = vec3(pos)
self.intangent = vec3(intangent)
self.outtangent = vec3(outtangent)
_uniform_size_constraint = UserSizeConstraint(1)
_null_size_constraint = UserSizeConstraint(0)
# BezierCurveGeom
class BezierCurveGeom(GeomObject):
"""Cubic Bezier curve.
"""
protocols.advise(instancesProvide=[IGeometry])
def __init__(self,
pnts = None,
closed = False,
epsilon = 0.01,
subdiv = 4,
show_tangents = False):
"""Constructor.
pnts is a list of BezierPoint objects.
"""
GeomObject.__init__(self)
# Epsilon value for segLength()
self.epsilon = epsilon
# Number of subdivisions for drawGL()
self.subdiv = subdiv
# Show tangents or not?
self.show_tangents = show_tangents
# Create the slots...
# The end points of the Bezier segments, i.e. the curve passes
# through these points.
self.pnts_slot = Vec3ArraySlot()
self._var_sizeconstraint = LinearSizeConstraint(self.pnts_slot,1,0)
if closed:
b = 0
else:
b = -2
self._vtx_sizeconstraint = LinearSizeConstraint(self.pnts_slot,3,b)
# The incoming tangent for point i
self.intangents_slot = Vec3ArraySlot(constraint=self._var_sizeconstraint)
# The outgoing tangent for point i
self.outtangents_slot = Vec3ArraySlot(constraint=self._var_sizeconstraint)
# Create aliases for the slot "value" attribute
self.pnts = self.pnts_slot
self.intangents = self.intangents_slot
self.outtangents = self.outtangents_slot
# Add the slots to the component
self.addSlot("pnts", self.pnts_slot)
self.addSlot("intangents", self.intangents_slot)
self.addSlot("outtangents", self.outtangents_slot)
self._closed = closed
if pnts!=None:
# Initialize the Bezier points...
self.pnts.resize(len(pnts))
for i,bp in enumerate(pnts):
self.pnts[i] = bp.pos
self.intangents[i] = bp.intangent
self.outtangents[i] = bp.outtangent
# A list of lengths (the lengths are accumulated, so it's
# the length of the entire curve until segment i)
self._seg_lengths = []
# Flag that indicates if all internal precomputed attributes have
# to be recomputed or if they are still valid.
self._internal_data_invalid = True
# Have the slots call the onCurveChanged() method whenever
# a point or a tangent is modified.
self._nf = NotificationForwarder(self.onCurveChanged, self.onCurveResized)
self.pnts_slot.addDependent(self._nf)
self.intangents_slot.addDependent(self._nf)
self.outtangents_slot.addDependent(self._nf)
def _updateInternalData(self):
"""Precalculate the lengths of the Bezier segments.
"""
self._seg_lengths = []
sl = 0.0
if self._closed:
k = 0
else:
k = 1
for i in range(self.pnts.size()-k):
sl += self.segLength(self.segCtrlPoints(i), self.epsilon)
self._seg_lengths.append(sl)
self._internal_data_invalid = False
# def uniformCount(self):
# return 1
# def varyingCount(self):
# # One value per segment boundary, i.e.
# # self.numsegs for periodic curves and
# # self.numsegs+1 for non-periodic curves
# # which can be reduced to self.pnts.size() for both cases
# # (as the curve points just mark segment boundaries)
# return self.pnts.size()
# def vertexCount(self):
# # One value per control points,
# n = 3*self.numsegs
# if not self._closed:
# n += 1
# return n
# slotSizeConstraint
def slotSizeConstraint(self, storage):
global _uniform_size_constraint
if storage==UNIFORM:
return _uniform_size_constraint
elif storage==VARYING:
return self._var_sizeconstraint
elif storage==VERTEX:
return self._vtx_sizeconstraint
else:
return _null_size_constraint
# boundingBox
def boundingBox(self):
"""Return the bounding box of the control polygon.
"""
bb = BoundingBox()
for i,p in enumerate(self.pnts):
bb.addPoint(p)
bb.addPoint(p+self.intangents[i])
bb.addPoint(p+self.outtangents[i])
return bb
# drawGL
def drawGL(self):
if self.pnts.size()==0:
return
glPushAttrib(GL_LIGHTING_BIT)
glDisable(GL_LIGHTING)
glColor3f(1,1,1)
# Draw the curve...
p0 = self.pnts[0]
in0 = self.intangents[0]
out0 = self.outtangents[0]
for i in range(1, self.pnts.size()):
p1 = self.pnts[i]
in1 = self.intangents[i]
out1 = self.outtangents[i]
self.segDrawGL([p0, p0+out0, p1+in1, p1], self.subdiv)
p0 = p1
in0 = in1
out0 = out1
# Draw an additional segment if the curve is closed...
if self._closed:
p0 = self.pnts[-1]
out0 = self.outtangents[-1]
p1 = self.pnts[0]
in1 = self.intangents[0]
self.segDrawGL([p0, p0+out0, p1+in1, p1], self.subdiv)
# Draw the in and out tangents...
if self.show_tangents:
for i in range(self.pnts.size()):
p = self.pnts[i]
pin = self.intangents[i]
pout = self.outtangents[i]
glBegin(GL_LINE_STRIP)
glColor3f(0,1,0)
v = p+pin
glVertex3d(v.x, v.y, v.z)
glVertex3d(p.x, p.y, p.z)
glColor3f(0,0,1)
glVertex3d(p.x, p.y, p.z)
v = p+pout
glVertex3d(v.x, v.y, v.z)
glEnd()
glPopAttrib()
# render
def render(self, matid):
if matid==0:
# Set Bezier basis
RiBasis(RiBezierBasis, RI_BEZIERSTEP, RiBezierBasis, RI_BEZIERSTEP)
# Create the list of curve vertices...
pnts = []
for i in range(self.numsegs):
ps = self.segCtrlPoints(i)
pnts += ps[0:3]
if self._closed:
wrap = RI_PERIODIC
else:
wrap = RI_NONPERIODIC
pnts.append(self.pnts[-1])
# Create parameter list...
params = {"P":pnts}
clss = ["constant", "uniform", "varying", "vertex",
"facevarying", "facevertex", "user"]
typs = ["integer", "float", "string", "color", "point",
"vector", "normal", "matrix", "hpoint"]
for name, storage, type, multiplicity in self.iterVariables():
cls = clss[abs(storage)]
typ = typs[abs(type)]
if cls=="user":
continue
s = self.slot(name)
params[name] = list(s)
if multiplicity==1:
decl = "%s %s"%(cls, typ)
else:
decl = "%s %s[%d]"%(cls, typ, multiplicity)
RiDeclare(name, decl)
# Output a curve primitive
RiCurves(RI_CUBIC, [len(pnts)], wrap, params)
# eval
def eval(self, t):
"""Evaluate the curve at parameter t and return the curve point.
"""
# Segment number
seg = int(t)
# Segment parameter
t = t%1.0
# if seg>=self.pnts.size()-1:
# seg = self.pnts.size()-2
# t = 1.0
return self.segEval(t, self.segCtrlPoints(seg))
# evalFrame
def evalFrame(self, t):
"""Evaluate the curve at parameter t and return a coordinate frame.
"""
# Segment number
seg = int(t)
# Segment parameter
t = t%1.0
# if seg>=self.pnts.size()-1:
# seg = self.pnts.size()-2
# t = 1.0
return self.segEvalFrame(t, self.segCtrlPoints(seg))
# deriv
def deriv(self, t):
"""Return the derivative at parameter t.
"""
# Segment number
seg = int(t)
# Segment parameter
t = t%1.0
# if seg>=self.pnts.size()-1:
# seg = self.pnts.size()-2
# t = 1.0
return self.segDeriv(t, self.segCtrlPoints(seg))
# arcLen
def arcLen(self, t):
"""Return the arc length up to t.
"""
if self._internal_data_invalid:
self._updateInternalData()
seg = int(t)
t = t%1.0
if seg>=len(self._seg_lengths):
return self._seg_lengths[-1]
res = 0.0
if seg>0:
res = self._seg_lengths[seg-1]
b,c = self.segSplit(self.segCtrlPoints(seg), t)
return res + self.segLength(b, self.epsilon)
# length
def length(self):
"""Return the length of the entire curve.
"""
if self._internal_data_invalid:
self._updateInternalData()
if len(self._seg_lengths)==0:
return 0.0
else:
return self._seg_lengths[-1]
def eval0(self, t):
if self._internal_data_invalid:
self._updateInternalData()
# Determine the Bezier segment index we're currently in
seg = bisect.bisect_right(self._seg_lengths, t)
if seg==self.pnts.size()-1:
return self.pnts[-1], vec3(), vec3()
if seg>0:
t -= self._seg_lengths[seg-1]
p,t = self.segEval0(t, self.segCtrlPoints(seg))
if p!=None:
return p, vec3(), vec3()
else:
return self.pnts[-1], vec3(), vec3()
# onCurveChanged
def onCurveChanged(self, start, end):
"""This method is called whenever a point or tangent is modified.
"""
self._internal_data_invalid = True
# onCurveResized
def onCurveResized(self, size):
"""This method is called whenever the number of points is modified.
"""
self._internal_data_invalid = True
## protected:
def segSmooth(self, seg):
b0,b1,b2,b3 = self.segCtrlPoints(seg-1)
d1 = b2-b1
d2 = b3-b2
self.outtangents[seg] = d2
self.intangents[seg+1] = self.pnts[seg]+3*d2-d1 - self.pnts[seg+1]
def segCtrlPoints(self, i):
"""Return the Bezier control points of segment i.
i may be an arbitrary segment number (the numbering is cyclic).
"""
s = self.pnts.size()
i = i%s
j = (i+1)%s
b0 = self.pnts[i]
b3 = self.pnts[j]
b1 = b0+self.outtangents[i]
b2 = b3+self.intangents[j]
return [b0, b1, b2, b3]
# segEval0
def segEval0(self, t, ctrlpnts, eps=0.001):
"""
Return value: (pnt, t2)
pnt is None if t>length. t2 is t-length.
"""
l1 = self.segHullLength(ctrlpnts)
b,c = self.segSplit(ctrlpnts)
bl = self.segHullLength(b)
cl = self.segHullLength(c)
l2 = bl+cl
# Is the result accurate enough? then return the current result,
# otherwise subdivide further
if l1-l2<=eps:
p,t = self.segHullEval0(t, b)
if p!=None:
return p,t
p,t = self.segHullEval0(t, c)
if p!=None:
return p,t
return None, t
else:
p,t = self.segEval0(t, b, eps=eps)
if p!=None:
return p,t
p,t = self.segEval0(t, c, eps=eps)
if p!=None:
return p,t
return None, t
def segHullEval0(self, t, ctrlpnts):
b0,b1,b2,b3 = ctrlpnts
# 1st segment
d = (b1-b0).length()
if t<d:
a = t/d
return (1-a)*b0 + a*b1, t
t-=d
# 2nd segment
d = (b2-b1).length()
if t<d:
a = t/d
return (1-a)*b1 + a*b2, t
t-=d
# 3rd segment
d = (b3-b2).length()
if t<d:
a = t/d
return (1-a)*b2 + a*b3, t
t-=d
return None, t
# segEval
def segEval(self, t, ctrlpnts):
"""Evaluate the curve at parameter t using the de Casteljau scheme.
t ranges from 0 to 1.
"""
_t = 1.0-t
b0,b1,b2,b3 = ctrlpnts
c0 = _t*b0 + t*b1
c1 = _t*b1 + t*b2
c2 = _t*b2 + t*b3
d0 = _t*c0 + t*c1
d1 = _t*c1 + t*c2
return _t*d0+t*d1
# segEvalFrame
def segEvalFrame(self, t, ctrlpnts):
"""Evaluate the curve at parameter t using the de Casteljau scheme.
t ranges from 0 to 1.
"""
_t = 1.0-t
b0,b1,b2,b3 = ctrlpnts
c0 = _t*b0 + t*b1
c1 = _t*b1 + t*b2
c2 = _t*b2 + t*b3
d0 = _t*c0 + t*c1
d1 = _t*c1 + t*c2
e0 = _t*d0+t*d1
return e0, 3*(d1-d0), 6*(c2-2*c1+c0)
# segDeriv
def segDeriv(self, t, ctrlpnts):
"""Return the derivation at t.
t ranges from 0 to 1.
"""
_t = 1.0-t
b0,b1,b2,b3 = ctrlpnts
c0 = _t*b0 + t*b1
c1 = _t*b1 + t*b2
c2 = _t*b2 + t*b3
d0 = _t*c0 + t*c1
d1 = _t*c1 + t*c2
return 3*(d1-d0)
# segDrawGL
def segDrawGL(self, ctrlpnts, subdiv=4):
"""Draw one Bezier segment.
Actually the control polygon is drawn after a number of
subdivisions.
"""
if subdiv==0:
b0,b1,b2,b3 = ctrlpnts
glBegin(GL_LINE_STRIP)
glVertex3d(b0.x, b0.y, b0.z)
glVertex3d(b1.x, b1.y, b1.z)
glVertex3d(b2.x, b2.y, b2.z)
glVertex3d(b3.x, b3.y, b3.z)
glEnd()
else:
b,c = self.segSplit(ctrlpnts)
self.segDrawGL(b, subdiv=subdiv-1)
self.segDrawGL(c, subdiv=subdiv-1)
# segLength
def segLength(self, ctrlpnts, eps=0.001):
"""Return the length of one Bezier segment.
ctrlpnts is a list of four vec3s.
"""
l1 = self.segHullLength(ctrlpnts)
b,c = self.segSplit(ctrlpnts)
bl = self.segHullLength(b)
cl = self.segHullLength(c)
l2 = bl+cl
# Is the result accurate enough? then return the current result,
# otherwise subdivide further
if l1-l2<=eps:
return l2
else:
return self.segLength(b,eps)+self.segLength(c,eps)
# segHullLength
def segHullLength(self, ctrlpnts):
"""Return the length of the control "polygon".
"""
b0,b1,b2,b3 = ctrlpnts
return (b1-b0).length() + (b2-b1).length() + (b3-b2).length()
# segSplit
def segSplit(self, ctrlpnts, t=0.5):
"""Split a segment into two segments.
"""
b0,b1,b2,b3 = ctrlpnts
_t = 1.0-t
c0 = _t*b0 + t*b1
c1 = _t*b1 + t*b2
c2 = _t*b2 + t*b3
d0 = _t*c0 + t*c1
d1 = _t*c1 + t*c2
e0 = _t*d0 + t*d1
return [b0,c0,d0,e0], [e0,d1,c2,b3]
# "paraminterval" property...
def _getParamInterval(self):
"""Return the current parameter interval.
This method is used for retrieving the \a paraminterval property.
\return (t_min, t_max)
"""
if self._closed:
return 0, self.pnts.size()
else:
return 0, self.pnts.size()-1
paraminterval = property(_getParamInterval, None, None, "Parameter range")
# "closed" property...
def _getClosed(self):
return self._closed
def _setClosed(self, c):
if c==self._closed:
return
self._closed = c
self._internal_data_invalid = True
# Update the size constraint for vertex variables
if self._closed:
self._vtx_sizeconstraint.setCoeffs(3,0)
else:
self._vtx_sizeconstraint.setCoeffs(3,-2)
closed = property(_getClosed, _setClosed, None, "Closed")
# "numsegs" property...
def _getNumSegs(self):
if self._closed:
return self.pnts.size()
else:
return self.pnts.size()-1
numsegs = property(_getNumSegs, None, None, "Number of Bezier segments")
|